[Sync] Add logic to reassociate tab nodes after restart.

Change SessionModelAssociator and TabNodePool to reuse old tab nodes.
Old tab nodes are now added to TabNodePool and if there is an
existing old tab node with syncid of tab, the tab is reassociated
with that node.
Note: In order for tab reassociation to work, the syncid of a tab
should be persisted. This CL just introduces the sync changes for
reassociating tab nodes. Persisting syncid with tab will be done
in a separate CL for Desktop, and in a downstream CL for Android.

BUG=139670,12549,139666
TEST=Includes unit tests + MANUAL testing on Android.

Review URL: https://chromiumcodereview.appspot.com/16421003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@208024 0039d316-1c4b-4281-b951-d872f2087c98
parent 6af41783
......@@ -130,3 +130,10 @@ static jint Init(JNIEnv* env,
reinterpret_cast<WindowAndroid*>(window_android_ptr));
return reinterpret_cast<jint>(tab);
}
int64 TestShellTab::GetSyncId() const {
NOTIMPLEMENTED();
return 0;
}
void TestShellTab::SetSyncId(int64 sync_id) { NOTIMPLEMENTED(); }
......@@ -68,6 +68,9 @@ class TestShellTab : public TabAndroid {
virtual void RunExternalProtocolDialog(const GURL& url) OVERRIDE;
virtual int64 GetSyncId() const OVERRIDE;
virtual void SetSyncId(int64 sync_id) OVERRIDE;
// Register the Tab's native methods through JNI.
static bool RegisterTestShellTab(JNIEnv* env);
......
......@@ -81,7 +81,12 @@ class TabAndroid {
// derived classes may remove their implementation first.
virtual void RunExternalProtocolDialog(const GURL& url);
// Used by sync to get/set the sync id of tab.
virtual int64 GetSyncId() const = 0;
virtual void SetSyncId(int64 sync_id) = 0;
static bool RegisterTabAndroid(JNIEnv* env);
protected:
virtual ~TabAndroid();
......
......@@ -180,6 +180,7 @@ bool SessionModelAssociator::AssociateWindows(bool reload_tabs,
synced_session_tracker_.ResetSessionTracking(local_tag);
std::set<SyncedWindowDelegate*> windows =
SyncedWindowDelegate::GetSyncedWindowDelegates();
std::set<int64> used_sync_ids;
for (std::set<SyncedWindowDelegate*>::const_iterator i =
windows.begin(); i != windows.end(); ++i) {
// Make sure the window has tabs and a viewable window. The viewable window
......@@ -210,13 +211,34 @@ bool SessionModelAssociator::AssociateWindows(bool reload_tabs,
bool found_tabs = false;
for (int j = 0; j < (*i)->GetTabCount(); ++j) {
SessionID::id_type tab_id = (*i)->GetTabIdAt(j);
SyncedTabDelegate* synced_tab = (*i)->GetTabAt(j);
// GetTabAt can return a null tab; in that case just skip it.
if (!synced_tab)
continue;
if (!synced_tab->HasWebContents()) {
// For tabs without WebContents update the |tab_id|, as it could have
// changed after a session restore.
// Note: We cannot check if a tab is valid if it has no WebContents.
// We assume any such tab is valid and leave the contents of
// corresponding sync node unchanged.
if (synced_tab->GetSyncId() > syncer::kInvalidId &&
tab_id > TabNodePool::kInvalidTabID) {
UpdateTabIdIfNecessary(synced_tab->GetSyncId(), tab_id);
found_tabs = true;
used_sync_ids.insert(synced_tab->GetSyncId());
window_s.add_tab(tab_id);
}
continue;
}
if (reload_tabs) {
SyncedTabDelegate* tab = (*i)->GetTabAt(j);
// It's possible for GetTabAt to return a tab which has no web
// contents. We can assume this means the tab already existed but
// hasn't changed, so no need to reassociate.
if (tab && tab->HasWebContents() && !AssociateTab(*tab, error)) {
if (synced_tab->HasWebContents() &&
!AssociateTab(synced_tab, error)) {
// Association failed. Either we need to re-associate, or this is an
// unrecoverable error.
return false;
......@@ -231,6 +253,7 @@ bool SessionModelAssociator::AssociateWindows(bool reload_tabs,
const SessionTab* tab = NULL;
if (synced_session_tracker_.LookupSessionTab(local_tag, tab_id, &tab)) {
found_tabs = true;
used_sync_ids.insert(synced_tab->GetSyncId());
window_s.add_tab(tab_id);
}
}
......@@ -250,6 +273,9 @@ bool SessionModelAssociator::AssociateWindows(bool reload_tabs,
}
}
}
// Free old sync nodes.
tab_pool_.FreeUnusedTabNodes(used_sync_ids);
// Free memory for closed windows and tabs.
synced_session_tracker_.CleanupSession(local_tag);
......@@ -285,19 +311,20 @@ bool SessionModelAssociator::AssociateTabs(
for (std::vector<SyncedTabDelegate*>::const_iterator i = tabs.begin();
i != tabs.end();
++i) {
if (!AssociateTab(**i, error))
if (!AssociateTab(*i, error))
return false;
}
if (waiting_for_change_) QuitLoopForSubtleTesting();
return true;
}
bool SessionModelAssociator::AssociateTab(const SyncedTabDelegate& tab,
bool SessionModelAssociator::AssociateTab(SyncedTabDelegate* const tab,
syncer::SyncError* error) {
DCHECK(CalledOnValidThread());
DCHECK(tab->HasWebContents());
int64 sync_id;
SessionID::id_type tab_id = tab.GetSessionId();
if (tab.IsBeingDestroyed()) {
SessionID::id_type tab_id = tab->GetSessionId();
if (tab->IsBeingDestroyed()) {
// This tab is closing.
TabLinksMap::iterator tab_iter = tab_map_.find(tab_id);
if (tab_iter == tab_map_.end()) {
......@@ -309,37 +336,43 @@ bool SessionModelAssociator::AssociateTab(const SyncedTabDelegate& tab,
return true;
}
if (!ShouldSyncTab(tab))
if (!ShouldSyncTab(*tab))
return true;
TabLinksMap::iterator tab_map_iter = tab_map_.find(tab_id);
TabLink* tab_link = NULL;
if (tab_map_iter == tab_map_.end()) {
// This is a new tab, get a sync node for it.
sync_id = tab_pool_.GetFreeTabNode();
if (sync_id == syncer::kInvalidId) {
if (error) {
*error = error_handler_->CreateAndUploadError(
FROM_HERE,
"Received invalid tab node from tab pool.",
model_type());
sync_id = tab->GetSyncId();
// if there is an old sync node for the tab, reuse it.
if (!tab_pool_.ReassociateTabNode(sync_id, tab_id)) {
// This is a new tab, get a sync node for it.
sync_id = tab_pool_.GetFreeTabNode();
if (sync_id == syncer::kInvalidId) {
if (error) {
*error = error_handler_->CreateAndUploadError(
FROM_HERE,
"Received invalid tab node from tab pool.",
model_type());
}
return false;
}
return false;
tab_pool_.AssociateTabNode(sync_id, tab_id);
tab->SetSyncId(sync_id);
}
tab_link = new TabLink(sync_id, &tab);
tab_link = new TabLink(sync_id, tab);
tab_map_[tab_id] = make_linked_ptr<TabLink>(tab_link);
} else {
// This tab is already associated with a sync node, reuse it.
// Note: on some platforms the tab object may have changed, so we ensure
// the tab link is up to date.
tab_link = tab_map_iter->second.get();
tab_map_iter->second->set_tab(&tab);
tab_map_iter->second->set_tab(tab);
}
DCHECK(tab_link);
DCHECK_NE(tab_link->sync_id(), syncer::kInvalidId);
DVLOG(1) << "Reloading tab " << tab_id << " from window "
<< tab.GetWindowId();
<< tab->GetWindowId();
return WriteTabContentsToSyncModel(tab_link, error);
}
......@@ -553,7 +586,7 @@ syncer::SyncError SessionModelAssociator::AssociateModels(
// Ensure that we disassociated properly, otherwise memory might leak.
DCHECK(synced_session_tracker_.Empty());
DCHECK_EQ(0U, tab_pool_.capacity());
DCHECK_EQ(0U, tab_pool_.Capacity());
local_session_syncid_ = syncer::kInvalidId;
......@@ -649,7 +682,7 @@ syncer::SyncError SessionModelAssociator::DisassociateModels() {
favicon_cache_.RemoveLegacyDelegate();
synced_session_tracker_.Clear();
tab_map_.clear();
tab_pool_.clear();
tab_pool_.Clear();
local_session_syncid_ = syncer::kInvalidId;
current_machine_tag_ = "";
current_session_name_ = "";
......@@ -680,7 +713,7 @@ void SessionModelAssociator::InitializeCurrentMachineTag(
prefs.SetSyncSessionsGUID(current_machine_tag_);
}
tab_pool_.set_machine_tag(current_machine_tag_);
tab_pool_.SetMachineTag(current_machine_tag_);
}
bool SessionModelAssociator::GetSyncedFaviconForPageURL(
......@@ -694,7 +727,7 @@ bool SessionModelAssociator::UpdateAssociationsFromSyncModel(
syncer::WriteTransaction* trans,
syncer::SyncError* error) {
DCHECK(CalledOnValidThread());
DCHECK(tab_pool_.empty());
DCHECK(tab_pool_.Empty());
DCHECK_EQ(local_session_syncid_, syncer::kInvalidId);
// Iterate through the nodes and associate any foreign sessions.
......@@ -731,24 +764,22 @@ bool SessionModelAssociator::UpdateAssociationsFromSyncModel(
current_session_name_ = specifics.header().client_name();
}
} else {
if (specifics.has_header()) {
LOG(WARNING) << "Found more than one session header node with local "
<< " tag.";
} else if (!specifics.has_tab()) {
LOG(WARNING) << "Found local node with no header or tag field.";
if (specifics.has_header() || !specifics.has_tab() ||
!specifics.has_tab_node_id() || !specifics.tab().has_tab_id()) {
LOG(WARNING) << "Found invalid session node, deleting.";
sync_node.Tombstone();
} else {
// This is a valid old tab node, add it to the pool so it can be
// reused for reassociation.
SessionID tab_id;
tab_id.set_id(specifics.tab().tab_id());
tab_pool_.AddTabNode(id, tab_id, specifics.tab_node_id());
}
// TODO(zea): fix this once we add support for reassociating
// pre-existing tabs with pre-existing tab nodes. We'll need to load
// the tab_node_id and ensure the tab_pool_ keeps track of them.
sync_node.Tombstone();
}
}
id = next_id;
}
// After updating from sync model all tabid's should be free.
DCHECK(tab_pool_.full());
return true;
}
......@@ -1151,4 +1182,28 @@ bool SessionModelAssociator::CryptoReadyIfNecessary() {
sync_service_->IsCryptographerReady(&trans);
}
void SessionModelAssociator::UpdateTabIdIfNecessary(
int64 sync_id,
SessionID::id_type new_tab_id) {
DCHECK_NE(sync_id, syncer::kInvalidId);
SessionID::id_type old_tab_id = tab_pool_.GetTabIdFromSyncId(sync_id);
if (old_tab_id != new_tab_id) {
// Rewrite tab id if required.
syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
syncer::WriteNode tab_node(&trans);
if (tab_node.InitByIdLookup(sync_id) == syncer::BaseNode::INIT_OK) {
sync_pb::SessionSpecifics session_specifics =
tab_node.GetSessionSpecifics();
DCHECK(session_specifics.has_tab());
if (session_specifics.has_tab()) {
sync_pb::SessionTab* tab_s = session_specifics.mutable_tab();
tab_s->set_tab_id(new_tab_id);
tab_node.SetSessionSpecifics(session_specifics);
// Update tab node pool with the new association.
tab_pool_.ReassociateTabNode(sync_id, new_tab_id);
}
}
}
}
} // namespace browser_sync
......@@ -108,10 +108,10 @@ class SessionModelAssociator
// Reassociates a single tab with the sync model. Will check if the tab
// already is associated with a sync node and allocate one if necessary.
// |error| gets set if any association error occurred.
// |tab| will be updated with sync id if necessary.
// Returns: false if the local session's sync nodes were deleted and
// reassociation is necessary, true otherwise.
bool AssociateTab(const SyncedTabDelegate& tab,
syncer::SyncError* error);
bool AssociateTab(SyncedTabDelegate* const tab, syncer::SyncError* error);
// Load any foreign session info stored in sync db and update the sync db
// with local client data. Processes/reuses any sync nodes owned by this
......@@ -329,6 +329,9 @@ class SessionModelAssociator
// invalid url's.
bool TabHasValidEntry(const SyncedTabDelegate& tab) const;
// Update the tab id of the node associated with |sync_id| to |new_tab_id|.
void UpdateTabIdIfNecessary(int64 sync_id, SessionID::id_type new_tab_id);
// For testing only.
void QuitLoopForSubtleTesting();
......
......@@ -190,6 +190,8 @@ class SyncedTabDelegateMock : public SyncedTabDelegate {
const std::vector<const content::NavigationEntry*>*());
MOCK_CONST_METHOD0(IsPinned, bool());
MOCK_CONST_METHOD0(HasWebContents, bool());
MOCK_CONST_METHOD0(GetSyncId, int64());
MOCK_METHOD1(SetSyncId, void(int64));
};
class SyncRefreshListener : public content::NotificationObserver {
......
......@@ -54,6 +54,9 @@ class SyncedTabDelegate {
virtual bool IsPinned() const = 0;
virtual bool HasWebContents() const = 0;
// Session sync related methods.
virtual int64 GetSyncId() const = 0;
virtual void SetSyncId(int64 sync_id) = 0;
// Returns the SyncedTabDelegate associated with WebContents.
static SyncedTabDelegate* ImplFromWebContents(
content::WebContents* web_contents);
......
......@@ -106,6 +106,14 @@ SyncedTabDelegateAndroid::GetBlockedNavigations() const {
->GetBlockedNavigations();
}
int64 SyncedTabDelegateAndroid::GetSyncId() const {
return tab_android_->GetSyncId();
}
void SyncedTabDelegateAndroid::SetSyncId(int64 sync_id) {
tab_android_->SetSyncId(sync_id);
}
// static
SyncedTabDelegate* SyncedTabDelegate::ImplFromWebContents(
content::WebContents* web_contents) {
......
......@@ -38,6 +38,8 @@ class SyncedTabDelegateAndroid : public browser_sync::SyncedTabDelegate {
virtual content::NavigationEntry* GetActiveEntry() const OVERRIDE;
virtual bool IsPinned() const OVERRIDE;
virtual bool HasWebContents() const OVERRIDE;
virtual int64 GetSyncId() const OVERRIDE;
virtual void SetSyncId(int64 sync_id) OVERRIDE;
// Managed user related methods.
......
......@@ -20,11 +20,10 @@ static const char kNoSessionsFolderError[] =
"Server did not create the top-level sessions node. We "
"might be running against an out-of-date server.";
TabNodePool::TabNodePool(
ProfileSyncService* sync_service)
: tab_pool_fp_(-1),
sync_service_(sync_service) {
}
static const size_t kMaxNumberOfFreeNodes = 25;
TabNodePool::TabNodePool(ProfileSyncService* sync_service)
: max_used_tab_node_id_(0), sync_service_(sync_service) {}
TabNodePool::~TabNodePool() {}
......@@ -35,14 +34,30 @@ std::string TabNodePool::TabIdToTag(
return base::StringPrintf("%s %" PRIuS "", machine_tag.c_str(), tab_node_id);
}
void TabNodePool::AddTabNode(int64 sync_id) {
tab_syncid_pool_.resize(tab_syncid_pool_.size() + 1);
tab_syncid_pool_[static_cast<size_t>(++tab_pool_fp_)] = sync_id;
void TabNodePool::AddTabNode(int64 sync_id,
const SessionID& tab_id,
size_t tab_node_id) {
DCHECK_GT(sync_id, syncer::kInvalidId);
DCHECK_GT(tab_id.id(), kInvalidTabID);
DCHECK(syncid_tabid_map_.find(sync_id) == syncid_tabid_map_.end());
syncid_tabid_map_[sync_id] = tab_id.id();
if (max_used_tab_node_id_ < tab_node_id)
max_used_tab_node_id_ = tab_node_id;
}
void TabNodePool::AssociateTabNode(int64 sync_id, SessionID::id_type tab_id) {
DCHECK_GT(sync_id, 0);
// Remove node from free node pool and associate it with tab.
std::set<int64>::iterator it = free_nodes_pool_.find(sync_id);
DCHECK(it != free_nodes_pool_.end());
free_nodes_pool_.erase(it);
DCHECK(syncid_tabid_map_.find(sync_id) == syncid_tabid_map_.end());
syncid_tabid_map_[sync_id] = tab_id;
}
int64 TabNodePool::GetFreeTabNode() {
DCHECK_GT(machine_tag_.length(), 0U);
if (tab_pool_fp_ == -1) {
if (free_nodes_pool_.empty()) {
// Tab pool has no free nodes, allocate new one.
syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
syncer::ReadNode root(&trans);
......@@ -51,7 +66,7 @@ int64 TabNodePool::GetFreeTabNode() {
LOG(ERROR) << kNoSessionsFolderError;
return syncer::kInvalidId;
}
size_t tab_node_id = tab_syncid_pool_.size();
size_t tab_node_id = ++max_used_tab_node_id_;
std::string tab_node_tag = TabIdToTag(machine_tag_, tab_node_id);
syncer::WriteNode tab_node(&trans);
syncer::WriteNode::InitUniqueByCreationResult result =
......@@ -69,23 +84,86 @@ int64 TabNodePool::GetFreeTabNode() {
specifics.set_tab_node_id(tab_node_id);
tab_node.SetSessionSpecifics(specifics);
// Grow the pool by 1 since we created a new node. We don't actually need
// to put the node's id in the pool now, since the pool is still empty.
// The id will be added when that tab is closed and the node is freed.
tab_syncid_pool_.resize(tab_node_id + 1);
DVLOG(1) << "Adding sync node "
<< tab_node.GetId() << " to tab syncid pool";
// Grow the pool by 1 since we created a new node.
DVLOG(1) << "Adding sync node " << tab_node.GetId()
<< " to tab syncid pool";
free_nodes_pool_.insert(tab_node.GetId());
return tab_node.GetId();
} else {
// There are nodes available, grab next free and decrement free pointer.
return tab_syncid_pool_[static_cast<size_t>(tab_pool_fp_--)];
// Return the next free node.
return *free_nodes_pool_.begin();
}
}
void TabNodePool::FreeTabNode(int64 sync_id) {
// Pool size should always match # of free tab nodes.
DCHECK_LT(tab_pool_fp_, static_cast<int64>(tab_syncid_pool_.size()));
tab_syncid_pool_[static_cast<size_t>(++tab_pool_fp_)] = sync_id;
SyncIDToTabIDMap::iterator it = syncid_tabid_map_.find(sync_id);
DCHECK(it != syncid_tabid_map_.end());
syncid_tabid_map_.erase(it);
DCHECK(free_nodes_pool_.find(sync_id) == free_nodes_pool_.end());
// If number of free nodes exceed number of maximum allowed free nodes,
// delete the sync node.
if (free_nodes_pool_.size() < kMaxNumberOfFreeNodes) {
free_nodes_pool_.insert(sync_id);
} else {
syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
syncer::WriteNode tab_node(&trans);
if (tab_node.InitByIdLookup(sync_id) != syncer::BaseNode::INIT_OK) {
LOG(ERROR) << "Could not find sync node with id: " << sync_id;
return;
}
tab_node.Tombstone();
}
}
bool TabNodePool::ReassociateTabNode(int64 sync_id, SessionID::id_type tab_id) {
SyncIDToTabIDMap::iterator it = syncid_tabid_map_.find(sync_id);
if (it != syncid_tabid_map_.end()) {
syncid_tabid_map_[sync_id] = tab_id;
return true;
}
return false;
}
SessionID::id_type TabNodePool::GetTabIdFromSyncId(int64 sync_id) const {
SyncIDToTabIDMap::const_iterator it = syncid_tabid_map_.find(sync_id);
if (it != syncid_tabid_map_.end()) {
return it->second;
}
return kInvalidTabID;
}
void TabNodePool::FreeUnusedTabNodes(const std::set<int64>& used_sync_ids) {
for (SyncIDToTabIDMap::iterator it = syncid_tabid_map_.begin();
it != syncid_tabid_map_.end();) {
if (used_sync_ids.find(it->first) == used_sync_ids.end()) {
// This sync node is not used, return it to free node pool.
int64 sync_id = it->first;
++it;
FreeTabNode(sync_id);
} else {
++it;
}
}
}
// Clear tab pool.
void TabNodePool::Clear() {
free_nodes_pool_.clear();
syncid_tabid_map_.clear();
max_used_tab_node_id_ = 0;
}
size_t TabNodePool::Capacity() const {
return syncid_tabid_map_.size() + free_nodes_pool_.size();
}
bool TabNodePool::Empty() const { return free_nodes_pool_.empty(); }
bool TabNodePool::Full() { return syncid_tabid_map_.empty(); }
void TabNodePool::SetMachineTag(const std::string& machine_tag) {
machine_tag_ = machine_tag;
}
} // namespace browser_sync
......@@ -5,10 +5,13 @@
#ifndef CHROME_BROWSER_SYNC_GLUE_TAB_NODE_POOL_H_
#define CHROME_BROWSER_SYNC_GLUE_TAB_NODE_POOL_H_
#include <map>
#include <set>
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "base/gtest_prod_util.h"
#include "chrome/browser/sessions/session_id.h"
class ProfileSyncService;
......@@ -16,77 +19,97 @@ namespace browser_sync {
// A pool for managing free/used tab sync nodes. Performs lazy creation
// of sync nodes when necessary.
// Note: We make use of the following "id's"
// - a sync_id: an int64 used in |syncer::InitByIdLookup|
// - a tab_id: created by session service, unique to this client
// - a tab_node_id: the id for a particular sync tab node. This is used
// to generate the sync tab node tag through:
// tab_tag = StringPrintf("%s_%ui", local_session_tag, tab_node_id);
// tab_node_id and sync_id are both unique to a particular sync node. The
// difference is that tab_node_id is controlled by the model associator and
// is used when creating a new sync node, which returns the sync_id, created
// by the sync db.
class TabNodePool {
public:
explicit TabNodePool(ProfileSyncService* sync_service);
~TabNodePool();
enum InvalidTab {
kInvalidTabID = -1
};
// Build a sync tag from tab_node_id.
static std::string TabIdToTag(const std::string machine_tag,
size_t tab_node_id);
// Add a previously allocated tab sync node to our pool. Increases the size
// of tab_syncid_pool_ by one and marks the new tab node as free.
// Returns the sync_id for the next free tab node. If none are available,
// creates a new tab node and adds it to free nodes pool. The free node can
// then be used to associate with a tab by calling AssociateTabNode.
// Note: The node is considered free until it has been associated. Repeated
// calls to GetFreeTabNode will return the same sync_id until node has been
// associated.
int64 GetFreeTabNode();
// Removes the node from |syncid_tabid_map_| and returns the node to free
// pool.
void FreeTabNode(int64 sync_id);
// Removes |sync_id| from free node pool and associates it with |tab_id|.
// sync_id should be id of a free node. In order to associate a non free
// sync node, use ReassociateTabNode.
void AssociateTabNode(int64 sync_id, SessionID::id_type tab_id);
// Associate sync_id with tab_id but does not add it to free node pool.
// Note: this should only be called when we discover tab sync nodes from
// previous sessions, not for freeing tab nodes we created through
// GetFreeTabNode (use FreeTabNode below for that).
void AddTabNode(int64 sync_id);
// The difference between AddTabNode and AssociateTabNode is that
// AssociateTabNode requires the sync node to be free to be associated.
// AddTabNode just adds the association.
void AddTabNode(int64 sync_id, const SessionID& tab_id, size_t tab_node_id);
// Returns the sync_id for the next free tab node. If none are available,
// creates a new tab node.
// Note: We make use of the following "id's"
// - a sync_id: an int64 used in |syncer::InitByIdLookup|
// - a tab_id: created by session service, unique to this client
// - a tab_node_id: the id for a particular sync tab node. This is used
// to generate the sync tab node tag through:
// tab_tag = StringPrintf("%s_%ui", local_session_tag, tab_node_id);
// tab_node_id and sync_id are both unique to a particular sync node. The
// difference is that tab_node_id is controlled by the model associator and
// is used when creating a new sync node, which returns the sync_id, created
// by the sync db.
int64 GetFreeTabNode();
// Returns the tab_id for |sync_id| if it exists else returns kInvalidTabID.
SessionID::id_type GetTabIdFromSyncId(int64 sync_id) const;
// Return a tab node to our free pool.
// Note: the difference between FreeTabNode and AddTabNode is that
// FreeTabNode does not modify the size of |tab_syncid_pool_|, while
// AddTabNode increases it by one. In the case of FreeTabNode, the size of
// the |tab_syncid_pool_| should always be equal to the amount of tab nodes
// associated with this machine.
void FreeTabNode(int64 sync_id);
// Reassociates sync node with id |sync_id| with tab node with |tab_id|.
// Returns true if it is able to reassociate the node, false if a sync node
// with id |sync_id| is not associated with any tab.
bool ReassociateTabNode(int64 sync_id, SessionID::id_type tab_id);
// Returns any nodes with sync_ids not in |used_synced_ids| to free node pool.
void FreeUnusedTabNodes(const std::set<int64>& used_sync_ids);
// Clear tab pool.
void clear() {
tab_syncid_pool_.clear();
tab_pool_fp_ = -1;
}
void Clear();
// Return the number of tab nodes this client currently has allocated
// (including both free and used nodes)
size_t capacity() const { return tab_syncid_pool_.size(); }
size_t Capacity() const;
// Return empty status (all tab nodes are in use).
bool empty() const { return tab_pool_fp_ == -1; }
bool Empty() const;
// Return full status (no tab nodes are in use).
bool full() {
return tab_pool_fp_ == static_cast<int64>(tab_syncid_pool_.size())-1;
}
bool Full();
void set_machine_tag(const std::string& machine_tag) {
machine_tag_ = machine_tag;
}
void SetMachineTag(const std::string& machine_tag);
private:
// Pool of all available syncid's for tab's we have created.
std::vector<int64> tab_syncid_pool_;
friend class SyncTabNodePoolTest;
typedef std::map<int64, SessionID::id_type> SyncIDToTabIDMap;
// Stores mapping of sync_ids associated with tab_ids, these are the used
// nodes of tab node pool.
// The nodes in the map can be returned to free tab node pool by calling
// FreeTabNode(sync_id).
SyncIDToTabIDMap syncid_tabid_map_;
// The sync ids for the set of free sync nodes.
std::set<int64> free_nodes_pool_;
// Free pointer for tab pool. Only those node id's, up to and including the
// one indexed by the free pointer, are valid and free. The rest of the
// |tab_syncid_pool_| is invalid because the nodes are in use.
// To get the next free node, use tab_syncid_pool_[tab_pool_fp_--].
int64 tab_pool_fp_;
// The maximum used tab_node id for a sync node. A new sync node will always
// be created with max_used_tab_node_id_ + 1.
size_t max_used_tab_node_id_;
// The machiine tag associated with this tab pool. Used in the title of new
// The machine tag associated with this tab pool. Used in the title of new
// sync nodes.
std::string machine_tag_;
......
......@@ -8,80 +8,199 @@
namespace browser_sync {
class SyncTabNodePoolTest : public testing::Test {
protected:
SyncTabNodePoolTest() : pool_(NULL) { pool_.SetMachineTag("tag"); }
size_t GetMaxUsedTabNodeId() const { return pool_.max_used_tab_node_id_; }
TabNodePool pool_;
};
namespace {
typedef testing::Test SyncTabNodePoolTest;
TEST_F(SyncTabNodePoolTest, TabNodeIdIncreases) {
// max_used_tab_node_ always increases.
SessionID session_id;
session_id.set_id(1);
pool_.AddTabNode(4, session_id, 10);
EXPECT_EQ(10u, GetMaxUsedTabNodeId());
session_id.set_id(2);
pool_.AddTabNode(5, session_id, 1);
EXPECT_EQ(10u, GetMaxUsedTabNodeId());
session_id.set_id(3);
pool_.AddTabNode(6, session_id, 1000);
EXPECT_EQ(1000u, GetMaxUsedTabNodeId());
pool_.ReassociateTabNode(6, 500);
// Freeing a tab node does not change max_used_tab_node_id_.
pool_.FreeTabNode(4);
pool_.FreeUnusedTabNodes(std::set<int64>());
EXPECT_EQ(1000u, GetMaxUsedTabNodeId());
for (int i = 0; i < 3; ++i) {
pool_.AssociateTabNode(pool_.GetFreeTabNode(), i + 1);
EXPECT_EQ(1000u, GetMaxUsedTabNodeId());
}
EXPECT_EQ(1000u, GetMaxUsedTabNodeId());
}
TEST_F(SyncTabNodePoolTest, OldTabNodesAddAndRemove) {
// VerifyOldTabNodes are added.
// sync_id =4, tab_node_id = 1, tab_id = 1
SessionID session_id;
session_id.set_id(1);
pool_.AddTabNode(4, session_id, 1);
// sync_id = 5, tab_node_id = 5, tab_id = 2
session_id.set_id(2);
pool_.AddTabNode(5, session_id, 2);
EXPECT_EQ(2u, pool_.Capacity());
EXPECT_TRUE(pool_.Empty());
EXPECT_TRUE(pool_.ReassociateTabNode(4, 2));
EXPECT_TRUE(pool_.ReassociateTabNode(5, 1));
EXPECT_TRUE(pool_.Empty());
// Check free unused tab nodes returns the node to free node pool_.
std::set<int64> used_sync_ids;
used_sync_ids.insert(5);
pool_.FreeUnusedTabNodes(used_sync_ids);
// 4 should be returned to free node pool_.
EXPECT_EQ(2u, pool_.Capacity());
EXPECT_FALSE(pool_.Empty());
// 5 should still be in the associated nodes.
EXPECT_FALSE(pool_.Full());
pool_.FreeTabNode(5);
// 5 should be returned to free nodes pool and pool should be full.
EXPECT_TRUE(pool_.Full());
EXPECT_EQ(4, pool_.GetFreeTabNode());
pool_.AssociateTabNode(4, 1);
EXPECT_EQ(5, pool_.GetFreeTabNode());
pool_.AssociateTabNode(5, 1);
EXPECT_TRUE(pool_.Empty());
EXPECT_FALSE(pool_.Full());
}
TEST_F(SyncTabNodePoolTest, OldTabNodesReassociation) {
// VerifyOldTabNodes are reassociated correctly.
// sync_id =4, tab_node_id = 1, tab_id = 1
SessionID session_id;
session_id.set_id(1);
pool_.AddTabNode(4, session_id, 1);
// sync_id = 5, tab_node_id = 2, tab_id = 2
session_id.set_id(2);
pool_.AddTabNode(5, session_id, 2);
// sync_id = 5, tab_node_id = 3, tab_id =3
session_id.set_id(3);
pool_.AddTabNode(6, session_id, 3);
EXPECT_EQ(3u, pool_.Capacity());
EXPECT_TRUE(pool_.Empty());
// Free 5 and 6.
pool_.FreeTabNode(5);
pool_.FreeTabNode(6);
// 5 and 6 nodes should not get reassociated.
EXPECT_TRUE(pool_.ReassociateTabNode(4, 5));
EXPECT_FALSE(pool_.ReassociateTabNode(5, 6));
EXPECT_FALSE(pool_.ReassociateTabNode(6, 7));
// Free node pool should have 5 and 6.
EXPECT_FALSE(pool_.Empty());
EXPECT_EQ(3u, pool_.Capacity());
// Free all nodes
pool_.FreeUnusedTabNodes(std::set<int64>());
EXPECT_TRUE(pool_.Full());
std::set<int64> free_sync_ids;
for (int i = 0; i < 3; ++i) {
free_sync_ids.insert(pool_.GetFreeTabNode());
// GetFreeTabNode will return the same value till the node is
// reassociated.
pool_.AssociateTabNode(pool_.GetFreeTabNode(), i + 1);
}
EXPECT_TRUE(pool_.Empty());
EXPECT_EQ(3u, free_sync_ids.size());
EXPECT_EQ(1u, free_sync_ids.count(4));
EXPECT_EQ(1u, free_sync_ids.count(5));
EXPECT_EQ(1u, free_sync_ids.count(6));
}
TEST_F(SyncTabNodePoolTest, Init) {
TabNodePool pool(NULL);
pool.set_machine_tag("tag");
ASSERT_TRUE(pool.empty());
ASSERT_TRUE(pool.full());
EXPECT_TRUE(pool_.Empty());
EXPECT_TRUE(pool_.Full());
}
TEST_F(SyncTabNodePoolTest, AddGet) {
TabNodePool pool(NULL);
pool.set_machine_tag("tag");
pool.AddTabNode(5);
pool.AddTabNode(10);
ASSERT_FALSE(pool.empty());
ASSERT_TRUE(pool.full());
ASSERT_EQ(2U, pool.capacity());
ASSERT_EQ(10, pool.GetFreeTabNode()); // Returns last free tab.
ASSERT_FALSE(pool.empty());
ASSERT_FALSE(pool.full());
ASSERT_EQ(2U, pool.capacity());
ASSERT_EQ(5, pool.GetFreeTabNode()); // Returns last free tab.
SessionID session_id;
session_id.set_id(1);
pool_.AddTabNode(5, session_id, 1);
session_id.set_id(2);
pool_.AddTabNode(10, session_id, 2);
pool_.FreeUnusedTabNodes(std::set<int64>());
EXPECT_FALSE(pool_.Empty());
EXPECT_TRUE(pool_.Full());
EXPECT_EQ(2U, pool_.Capacity());
EXPECT_EQ(5, pool_.GetFreeTabNode());
pool_.AssociateTabNode(5, 1);
EXPECT_FALSE(pool_.Empty());
EXPECT_FALSE(pool_.Full());
EXPECT_EQ(2U, pool_.Capacity());
// 5 is now used, should return 10.
EXPECT_EQ(10, pool_.GetFreeTabNode());
}
TEST_F(SyncTabNodePoolTest, All) {
TabNodePool pool(NULL);
pool.set_machine_tag("tag");
ASSERT_TRUE(pool.empty());
ASSERT_TRUE(pool.full());
ASSERT_EQ(0U, pool.capacity());
pool.AddTabNode(5);
pool.AddTabNode(10);
ASSERT_FALSE(pool.empty());
ASSERT_TRUE(pool.full());
ASSERT_EQ(2U, pool.capacity());
ASSERT_EQ(10, pool.GetFreeTabNode()); // Returns last free tab.
ASSERT_FALSE(pool.empty());
ASSERT_FALSE(pool.full());
ASSERT_EQ(2U, pool.capacity());
ASSERT_EQ(5, pool.GetFreeTabNode()); // Returns last free tab.
ASSERT_TRUE(pool.empty());
ASSERT_FALSE(pool.full());
ASSERT_EQ(2U, pool.capacity());
EXPECT_TRUE(pool_.Empty());
EXPECT_TRUE(pool_.Full());
EXPECT_EQ(0U, pool_.Capacity());
SessionID session_id;
session_id.set_id(1);
pool_.AddTabNode(5, session_id, 1);
session_id.set_id(2);
pool_.AddTabNode(10, session_id, 2);
// Free added nodes.
pool_.FreeUnusedTabNodes(std::set<int64>());
EXPECT_FALSE(pool_.Empty());
EXPECT_TRUE(pool_.Full());
EXPECT_EQ(2U, pool_.Capacity());
// GetFreeTabNode returns the lowest numbered free node.
EXPECT_EQ(5, pool_.GetFreeTabNode());
EXPECT_FALSE(pool_.Empty());
EXPECT_TRUE(pool_.Full());
EXPECT_EQ(2U, pool_.Capacity());
// Associate 5, next free node should be 10.
pool_.AssociateTabNode(5, 1);
EXPECT_EQ(10, pool_.GetFreeTabNode());
pool_.AssociateTabNode(10, 2);
EXPECT_TRUE(pool_.Empty());
EXPECT_FALSE(pool_.Full());
EXPECT_EQ(2U, pool_.Capacity());
// Release them in reverse order.
pool.FreeTabNode(10);
pool.FreeTabNode(5);
ASSERT_EQ(2U, pool.capacity());
ASSERT_FALSE(pool.empty());
ASSERT_TRUE(pool.full());
ASSERT_EQ(5, pool.GetFreeTabNode()); // Returns last free tab.
ASSERT_FALSE(pool.empty());
ASSERT_FALSE(pool.full());
ASSERT_EQ(2U, pool.capacity());
ASSERT_FALSE(pool.empty());
ASSERT_FALSE(pool.full());
ASSERT_EQ(2U, pool.capacity());
ASSERT_EQ(10, pool.GetFreeTabNode()); // Returns last free tab.
ASSERT_TRUE(pool.empty());
ASSERT_FALSE(pool.full());
ASSERT_EQ(2U, pool.capacity());
pool_.FreeTabNode(10);
pool_.FreeTabNode(5);
EXPECT_EQ(2U, pool_.Capacity());
EXPECT_FALSE(pool_.Empty());
EXPECT_TRUE(pool_.Full());
EXPECT_EQ(5, pool_.GetFreeTabNode());
EXPECT_FALSE(pool_.Empty());
EXPECT_TRUE(pool_.Full());
EXPECT_EQ(2U, pool_.Capacity());
EXPECT_FALSE(pool_.Empty());
EXPECT_TRUE(pool_.Full());
pool_.AssociateTabNode(5, 1);
EXPECT_EQ(2U, pool_.Capacity());
EXPECT_EQ(10, pool_.GetFreeTabNode());
pool_.AssociateTabNode(10, 2);
EXPECT_TRUE(pool_.Empty());
EXPECT_FALSE(pool_.Full());
EXPECT_EQ(2U, pool_.Capacity());
// Release them again.
pool.FreeTabNode(10);
pool.FreeTabNode(5);
ASSERT_FALSE(pool.empty());
ASSERT_TRUE(pool.full());
ASSERT_EQ(2U, pool.capacity());
pool.clear();
ASSERT_TRUE(pool.empty());
ASSERT_TRUE(pool.full());
ASSERT_EQ(0U, pool.capacity());
pool_.FreeTabNode(10);
pool_.FreeTabNode(5);
EXPECT_FALSE(pool_.Empty());
EXPECT_TRUE(pool_.Full());
EXPECT_EQ(2U, pool_.Capacity());
pool_.Clear();
EXPECT_TRUE(pool_.Empty());
EXPECT_TRUE(pool_.Full());
EXPECT_EQ(0U, pool_.Capacity());
}
} // namespace
......
......@@ -752,24 +752,26 @@ TEST_F(ProfileSyncServiceSessionTest, TabNodePoolEmpty) {
ASSERT_TRUE(create_root.success());
std::vector<int64> node_ids;
ASSERT_EQ(0U, model_associator_->tab_pool_.capacity());
ASSERT_TRUE(model_associator_->tab_pool_.empty());
ASSERT_TRUE(model_associator_->tab_pool_.full());
ASSERT_EQ(0U, model_associator_->tab_pool_.Capacity());
ASSERT_TRUE(model_associator_->tab_pool_.Empty());
ASSERT_TRUE(model_associator_->tab_pool_.Full());
const size_t num_ids = 10;
for (size_t i = 0; i < num_ids; ++i) {
int64 id = model_associator_->tab_pool_.GetFreeTabNode();
ASSERT_GT(id, -1);
node_ids.push_back(id);
// Associate with a tab node.
model_associator_->tab_pool_.AssociateTabNode(id, i + 1);
}
ASSERT_EQ(num_ids, model_associator_->tab_pool_.capacity());
ASSERT_TRUE(model_associator_->tab_pool_.empty());
ASSERT_FALSE(model_associator_->tab_pool_.full());
ASSERT_EQ(num_ids, model_associator_->tab_pool_.Capacity());
ASSERT_TRUE(model_associator_->tab_pool_.Empty());
ASSERT_FALSE(model_associator_->tab_pool_.Full());
for (size_t i = 0; i < num_ids; ++i) {
model_associator_->tab_pool_.FreeTabNode(node_ids[i]);
}
ASSERT_EQ(num_ids, model_associator_->tab_pool_.capacity());
ASSERT_FALSE(model_associator_->tab_pool_.empty());
ASSERT_TRUE(model_associator_->tab_pool_.full());
ASSERT_EQ(num_ids, model_associator_->tab_pool_.Capacity());
ASSERT_FALSE(model_associator_->tab_pool_.Empty());
ASSERT_TRUE(model_associator_->tab_pool_.Full());
}
// TODO(jhorwich): Re-enable when crbug.com/121487 addressed
......@@ -779,29 +781,34 @@ TEST_F(ProfileSyncServiceSessionTest, TabNodePoolNonEmpty) {
ASSERT_TRUE(create_root.success());
const size_t num_starting_nodes = 3;
SessionID session_id;
for (size_t i = 0; i < num_starting_nodes; ++i) {
model_associator_->tab_pool_.AddTabNode(i);
session_id.set_id(i + 1);
model_associator_->tab_pool_.AddTabNode(i + 1, session_id, i);
}
model_associator_->tab_pool_.FreeUnusedTabNodes(std::set<int64>());
std::vector<int64> node_ids;
ASSERT_EQ(num_starting_nodes, model_associator_->tab_pool_.capacity());
ASSERT_FALSE(model_associator_->tab_pool_.empty());
ASSERT_TRUE(model_associator_->tab_pool_.full());
ASSERT_EQ(num_starting_nodes, model_associator_->tab_pool_.Capacity());
ASSERT_FALSE(model_associator_->tab_pool_.Empty());
ASSERT_TRUE(model_associator_->tab_pool_.Full());
const size_t num_ids = 10;
for (size_t i = 0; i < num_ids; ++i) {
int64 id = model_associator_->tab_pool_.GetFreeTabNode();
ASSERT_GT(id, -1);
node_ids.push_back(id);
// Associate with a tab node.
model_associator_->tab_pool_.AssociateTabNode(id, i + 1);
}
ASSERT_EQ(num_ids, model_associator_->tab_pool_.capacity());
ASSERT_TRUE(model_associator_->tab_pool_.empty());
ASSERT_FALSE(model_associator_->tab_pool_.full());
ASSERT_EQ(num_ids, model_associator_->tab_pool_.Capacity());
ASSERT_TRUE(model_associator_->tab_pool_.Empty());
ASSERT_FALSE(model_associator_->tab_pool_.Full());
for (size_t i = 0; i < num_ids; ++i) {
model_associator_->tab_pool_.FreeTabNode(node_ids[i]);
}
ASSERT_EQ(num_ids, model_associator_->tab_pool_.capacity());
ASSERT_FALSE(model_associator_->tab_pool_.empty());
ASSERT_TRUE(model_associator_->tab_pool_.full());
ASSERT_EQ(num_ids, model_associator_->tab_pool_.Capacity());
ASSERT_FALSE(model_associator_->tab_pool_.Empty());
ASSERT_TRUE(model_associator_->tab_pool_.Full());
}
// Write a foreign session to a node, and then delete it.
......@@ -1165,7 +1172,7 @@ TEST_F(ProfileSyncServiceSessionTest, MissingLocalTabNode) {
ASSERT_FALSE(error.IsSet());
{
// Delete the first sync tab node.
std::string tab_tag = TabNodePool::TabIdToTag(local_tag, 0);
std::string tab_tag = TabNodePool::TabIdToTag(local_tag, 1);
syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
syncer::ReadNode root(&trans);
......
......@@ -25,7 +25,7 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(TabContentsSyncedTabDelegate);
TabContentsSyncedTabDelegate::TabContentsSyncedTabDelegate(
content::WebContents* web_contents)
: web_contents_(web_contents) {}
: web_contents_(web_contents), sync_session_id_(0) {}
TabContentsSyncedTabDelegate::~TabContentsSyncedTabDelegate() {}
......@@ -105,3 +105,11 @@ bool TabContentsSyncedTabDelegate::IsPinned() const {
}
bool TabContentsSyncedTabDelegate::HasWebContents() const { return true; }
int64 TabContentsSyncedTabDelegate::GetSyncId() const {
return sync_session_id_;
}
void TabContentsSyncedTabDelegate::SetSyncId(int64 sync_id) {
sync_session_id_ = sync_id;
}
......@@ -37,12 +37,15 @@ class TabContentsSyncedTabDelegate
GetBlockedNavigations() const OVERRIDE;
virtual bool IsPinned() const OVERRIDE;
virtual bool HasWebContents() const OVERRIDE;
virtual int64 GetSyncId() const OVERRIDE;
virtual void SetSyncId(int64 sync_id) OVERRIDE;
private:
explicit TabContentsSyncedTabDelegate(content::WebContents* web_contents);
friend class content::WebContentsUserData<TabContentsSyncedTabDelegate>;
content::WebContents* web_contents_;
int64 sync_session_id_;
DISALLOW_COPY_AND_ASSIGN(TabContentsSyncedTabDelegate);
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment