blob: d97ff6728aa866b0c717b6b77540b94b0d0b3f68 [file] [log] [blame]
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sync/syncable/syncable_write_transaction.h"
#include "sync/syncable/directory.h"
#include "sync/syncable/directory_change_delegate.h"
#include "sync/syncable/mutable_entry.h"
#include "sync/syncable/transaction_observer.h"
#include "sync/syncable/write_transaction_info.h"
namespace syncer {
namespace syncable {
const int64 kInvalidTransactionVersion = -1;
WriteTransaction::WriteTransaction(const tracked_objects::Location& location,
WriterTag writer, Directory* directory)
: BaseWriteTransaction(location, "WriteTransaction", writer, directory),
transaction_version_(NULL) {
Lock();
}
WriteTransaction::WriteTransaction(const tracked_objects::Location& location,
Directory* directory,
int64* transaction_version)
: BaseWriteTransaction(location, "WriteTransaction", SYNCAPI, directory),
transaction_version_(transaction_version) {
Lock();
if (transaction_version_)
*transaction_version_ = kInvalidTransactionVersion;
}
void WriteTransaction::TrackChangesTo(const EntryKernel* entry) {
if (!entry) {
return;
}
// Insert only if it's not already there.
const int64 handle = entry->ref(META_HANDLE);
EntryKernelMutationMap::iterator it = mutations_.lower_bound(handle);
if (it == mutations_.end() || it->first != handle) {
mutations_[handle].original = *entry;
}
}
ImmutableEntryKernelMutationMap WriteTransaction::RecordMutations() {
directory_->kernel_->transaction_mutex.AssertAcquired();
for (syncable::EntryKernelMutationMap::iterator it = mutations_.begin();
it != mutations_.end();) {
EntryKernel* kernel = directory()->GetEntryByHandle(it->first);
if (!kernel) {
NOTREACHED();
continue;
}
if (kernel->is_dirty()) {
it->second.mutated = *kernel;
++it;
} else {
DCHECK(!it->second.original.is_dirty());
// Not actually mutated, so erase from |mutations_|.
mutations_.erase(it++);
}
}
return ImmutableEntryKernelMutationMap(&mutations_);
}
void WriteTransaction::UnlockAndNotify(
const ImmutableEntryKernelMutationMap& mutations) {
// Work while transaction mutex is held.
ModelTypeSet models_with_changes;
bool has_mutations = !mutations.Get().empty();
if (has_mutations) {
models_with_changes = NotifyTransactionChangingAndEnding(mutations);
}
Unlock();
// Work after mutex is relased.
if (has_mutations) {
NotifyTransactionComplete(models_with_changes);
}
}
ModelTypeSet WriteTransaction::NotifyTransactionChangingAndEnding(
const ImmutableEntryKernelMutationMap& mutations) {
directory_->kernel_->transaction_mutex.AssertAcquired();
DCHECK(!mutations.Get().empty());
WriteTransactionInfo write_transaction_info(
directory_->kernel_->next_write_transaction_id,
from_here_, writer_, mutations);
++directory_->kernel_->next_write_transaction_id;
ImmutableWriteTransactionInfo immutable_write_transaction_info(
&write_transaction_info);
DirectoryChangeDelegate* const delegate = directory_->kernel_->delegate;
std::vector<int64> entry_changed;
if (writer_ == syncable::SYNCAPI) {
delegate->HandleCalculateChangesChangeEventFromSyncApi(
immutable_write_transaction_info, this, &entry_changed);
} else {
delegate->HandleCalculateChangesChangeEventFromSyncer(
immutable_write_transaction_info, this, &entry_changed);
}
UpdateTransactionVersion(entry_changed);
ModelTypeSet models_with_changes =
delegate->HandleTransactionEndingChangeEvent(
immutable_write_transaction_info, this);
directory_->kernel_->transaction_observer.Call(FROM_HERE,
&TransactionObserver::OnTransactionWrite,
immutable_write_transaction_info, models_with_changes);
return models_with_changes;
}
void WriteTransaction::NotifyTransactionComplete(
ModelTypeSet models_with_changes) {
directory_->kernel_->delegate->HandleTransactionCompleteChangeEvent(
models_with_changes);
}
void WriteTransaction::UpdateTransactionVersion(
const std::vector<int64>& entry_changed) {
syncer::ModelTypeSet type_seen;
for (uint32 i = 0; i < entry_changed.size(); ++i) {
MutableEntry entry(this, GET_BY_HANDLE, entry_changed[i]);
if (entry.good()) {
ModelType type = GetModelTypeFromSpecifics(entry.GetSpecifics());
if (type < FIRST_REAL_MODEL_TYPE)
continue;
if (!type_seen.Has(type)) {
directory_->IncrementTransactionVersion(type);
type_seen.Put(type);
}
entry.UpdateTransactionVersion(directory_->GetTransactionVersion(type));
}
}
if (!type_seen.Empty() && transaction_version_) {
DCHECK_EQ(1u, type_seen.Size());
*transaction_version_ = directory_->GetTransactionVersion(
type_seen.First().Get());
}
}
WriteTransaction::~WriteTransaction() {
const ImmutableEntryKernelMutationMap& mutations = RecordMutations();
MetahandleSet modified_handles;
for (EntryKernelMutationMap::const_iterator i = mutations.Get().begin();
i != mutations.Get().end(); ++i) {
modified_handles.insert(i->first);
}
directory()->CheckInvariantsOnTransactionClose(this, modified_handles);
// |CheckTreeInvariants| could have thrown an unrecoverable error.
if (unrecoverable_error_set_) {
HandleUnrecoverableErrorIfSet();
Unlock();
return;
}
UnlockAndNotify(mutations);
}
#define ENUM_CASE(x) case x: return #x; break
std::string WriterTagToString(WriterTag writer_tag) {
switch (writer_tag) {
ENUM_CASE(INVALID);
ENUM_CASE(SYNCER);
ENUM_CASE(AUTHWATCHER);
ENUM_CASE(UNITTEST);
ENUM_CASE(VACUUM_AFTER_SAVE);
ENUM_CASE(HANDLE_SAVE_FAILURE);
ENUM_CASE(PURGE_ENTRIES);
ENUM_CASE(SYNCAPI);
};
NOTREACHED();
return std::string();
}
#undef ENUM_CASE
} // namespace syncable
} // namespace syncer