1// Copyright (c) 2012 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/internal_api/public/engine/model_safe_worker.h"
6
7#include "base/bind.h"
8#include "base/json/json_writer.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/values.h"
11
12namespace syncer {
13
14base::DictionaryValue* ModelSafeRoutingInfoToValue(
15    const ModelSafeRoutingInfo& routing_info) {
16  base::DictionaryValue* dict = new base::DictionaryValue();
17  for (ModelSafeRoutingInfo::const_iterator it = routing_info.begin();
18       it != routing_info.end(); ++it) {
19    dict->SetString(ModelTypeToString(it->first),
20                    ModelSafeGroupToString(it->second));
21  }
22  return dict;
23}
24
25std::string ModelSafeRoutingInfoToString(
26    const ModelSafeRoutingInfo& routing_info) {
27  scoped_ptr<base::DictionaryValue> dict(
28      ModelSafeRoutingInfoToValue(routing_info));
29  std::string json;
30  base::JSONWriter::Write(dict.get(), &json);
31  return json;
32}
33
34ModelTypeSet GetRoutingInfoTypes(const ModelSafeRoutingInfo& routing_info) {
35  ModelTypeSet types;
36  for (ModelSafeRoutingInfo::const_iterator it = routing_info.begin();
37       it != routing_info.end(); ++it) {
38    types.Put(it->first);
39  }
40  return types;
41}
42
43ModelSafeGroup GetGroupForModelType(const ModelType type,
44                                    const ModelSafeRoutingInfo& routes) {
45  ModelSafeRoutingInfo::const_iterator it = routes.find(type);
46  if (it == routes.end()) {
47    if (type != UNSPECIFIED && type != TOP_LEVEL_FOLDER)
48      DVLOG(1) << "Entry does not belong to active ModelSafeGroup!";
49    return GROUP_PASSIVE;
50  }
51  return it->second;
52}
53
54std::string ModelSafeGroupToString(ModelSafeGroup group) {
55  switch (group) {
56    case GROUP_UI:
57      return "GROUP_UI";
58    case GROUP_DB:
59      return "GROUP_DB";
60    case GROUP_FILE:
61      return "GROUP_FILE";
62    case GROUP_HISTORY:
63      return "GROUP_HISTORY";
64    case GROUP_PASSIVE:
65      return "GROUP_PASSIVE";
66    case GROUP_PASSWORD:
67      return "GROUP_PASSWORD";
68    default:
69      NOTREACHED();
70      return "INVALID";
71  }
72}
73
74ModelSafeWorker::ModelSafeWorker(WorkerLoopDestructionObserver* observer)
75    : stopped_(false),
76      work_done_or_stopped_(false, false),
77      observer_(observer),
78      working_loop_(NULL) {
79}
80
81ModelSafeWorker::~ModelSafeWorker() {}
82
83void ModelSafeWorker::RequestStop() {
84  base::AutoLock al(stopped_lock_);
85
86  // Set stop flag but don't signal work_done_or_stopped_ to unblock sync loop
87  // because the worker may be working and depending on sync command object
88  // living on sync thread. his prevents any *further* tasks from being posted
89  // to worker threads (see DoWorkAndWaitUntilDone below), but note that one
90  // may already be posted.
91  stopped_ = true;
92}
93
94SyncerError ModelSafeWorker::DoWorkAndWaitUntilDone(const WorkCallback& work) {
95  {
96    base::AutoLock al(stopped_lock_);
97    if (stopped_)
98      return CANNOT_DO_WORK;
99
100    CHECK(!work_done_or_stopped_.IsSignaled());
101  }
102
103  return DoWorkAndWaitUntilDoneImpl(work);
104}
105
106bool ModelSafeWorker::IsStopped() {
107  base::AutoLock al(stopped_lock_);
108  return stopped_;
109}
110
111void ModelSafeWorker::WillDestroyCurrentMessageLoop() {
112  {
113    base::AutoLock al(stopped_lock_);
114    stopped_ = true;
115
116    // Must signal to unblock syncer if it's waiting for a posted task to
117    // finish. At this point, all pending tasks posted to the loop have been
118    // destroyed (see MessageLoop::~MessageLoop). So syncer will be blocked
119    // indefinitely without signaling here.
120    work_done_or_stopped_.Signal();
121
122    DVLOG(1) << ModelSafeGroupToString(GetModelSafeGroup())
123        << " worker stops on destruction of its working thread.";
124  }
125
126  {
127    base::AutoLock l(working_loop_lock_);
128    working_loop_ = NULL;
129  }
130
131  if (observer_)
132    observer_->OnWorkerLoopDestroyed(GetModelSafeGroup());
133}
134
135void ModelSafeWorker::SetWorkingLoopToCurrent() {
136  base::Callback<void(ModelSafeGroup)> unregister_done_callback;
137
138  {
139    base::AutoLock l(working_loop_lock_);
140    DCHECK(!working_loop_);
141
142    if (unregister_done_callback_.is_null()) {
143      // Expected case - UnregisterForLoopDestruction hasn't been called yet.
144      base::MessageLoop::current()->AddDestructionObserver(this);
145      working_loop_ = base::MessageLoop::current();
146    } else {
147      // Rare case which is possible when the model type thread remains
148      // blocked for the entire session and UnregisterForLoopDestruction ends
149      // up being called before this method. This method is posted unlike
150      // UnregisterForLoopDestruction - that's why they can end up being called
151      // out of order.
152      // In this case we skip the destruction observer registration
153      // and just invoke the callback stored at UnregisterForLoopDestruction.
154      DCHECK(stopped_);
155      unregister_done_callback = unregister_done_callback_;
156      unregister_done_callback_.Reset();
157    }
158  }
159
160  if (!unregister_done_callback.is_null()) {
161    unregister_done_callback.Run(GetModelSafeGroup());
162  }
163}
164
165void ModelSafeWorker::UnregisterForLoopDestruction(
166    base::Callback<void(ModelSafeGroup)> unregister_done_callback) {
167  base::AutoLock l(working_loop_lock_);
168  if (working_loop_ != NULL) {
169    // Normal case - observer registration has been already done.
170    // Delegate to the sync thread to do the actual unregistration in
171    // UnregisterForLoopDestructionAsync.
172    DCHECK_NE(base::MessageLoop::current(), working_loop_);
173    working_loop_->PostTask(
174        FROM_HERE,
175        base::Bind(&ModelSafeWorker::UnregisterForLoopDestructionAsync,
176                   this,
177                   unregister_done_callback));
178  } else {
179    // The working loop is still unknown, probably because the model type
180    // thread is blocked. Store the callback to be called from
181    // SetWorkingLoopToCurrent.
182    unregister_done_callback_ = unregister_done_callback;
183  }
184}
185
186void ModelSafeWorker::UnregisterForLoopDestructionAsync(
187    base::Callback<void(ModelSafeGroup)> unregister_done_callback) {
188  {
189    base::AutoLock l(working_loop_lock_);
190    if (!working_loop_)
191      return;
192    DCHECK_EQ(base::MessageLoop::current(), working_loop_);
193  }
194
195  DCHECK(stopped_);
196  base::MessageLoop::current()->RemoveDestructionObserver(this);
197  unregister_done_callback.Run(GetModelSafeGroup());
198}
199
200}  // namespace syncer
201