• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Chromium Authors
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 "components/metrics/structured/persistent_proto.h"
6 
7 #include <memory>
8 
9 #include "base/files/file_util.h"
10 #include "base/files/important_file_writer.h"
11 #include "base/logging.h"
12 #include "base/rand_util.h"
13 #include "base/task/sequenced_task_runner.h"
14 #include "base/task/task_traits.h"
15 #include "base/task/thread_pool.h"
16 #include "base/threading/scoped_blocking_call.h"
17 #include "components/metrics/structured/histogram_util.h"
18 #include "components/metrics/structured/storage.pb.h"
19 
20 namespace metrics {
21 namespace structured {
22 namespace {
23 
24 template <class T>
Read(const base::FilePath & filepath)25 std::pair<ReadStatus, std::unique_ptr<T>> Read(const base::FilePath& filepath) {
26   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
27                                                 base::BlockingType::MAY_BLOCK);
28   if (!base::PathExists(filepath)) {
29     return {ReadStatus::kMissing, nullptr};
30   }
31 
32   std::string proto_str;
33   if (!base::ReadFileToString(filepath, &proto_str)) {
34     return {ReadStatus::kReadError, nullptr};
35   }
36 
37   auto proto = std::make_unique<T>();
38   if (!proto->ParseFromString(proto_str)) {
39     return {ReadStatus::kParseError, nullptr};
40   }
41 
42   return {ReadStatus::kOk, std::move(proto)};
43 }
44 
Write(const base::FilePath & filepath,const std::string & proto_str)45 WriteStatus Write(const base::FilePath& filepath,
46                   const std::string& proto_str) {
47   const auto directory = filepath.DirName();
48   if (!base::DirectoryExists(directory)) {
49     base::CreateDirectory(directory);
50   }
51 
52   bool write_result;
53   {
54     base::ScopedBlockingCall scoped_blocking_call(
55         FROM_HERE, base::BlockingType::MAY_BLOCK);
56     write_result = base::ImportantFileWriter::WriteFileAtomically(
57         filepath, proto_str, "StructuredMetricsPersistentProto");
58   }
59 
60   if (!write_result) {
61     return WriteStatus::kWriteError;
62   }
63   return WriteStatus::kOk;
64 }
65 
66 }  // namespace
67 
68 template <class T>
PersistentProto(const base::FilePath & path,const base::TimeDelta write_delay,typename PersistentProto<T>::ReadCallback on_read,typename PersistentProto<T>::WriteCallback on_write)69 PersistentProto<T>::PersistentProto(
70     const base::FilePath& path,
71     const base::TimeDelta write_delay,
72     typename PersistentProto<T>::ReadCallback on_read,
73     typename PersistentProto<T>::WriteCallback on_write)
74     : path_(path),
75       write_delay_(write_delay),
76       on_read_(std::move(on_read)),
77       on_write_(std::move(on_write)) {
78   task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
79       {base::TaskPriority::BEST_EFFORT, base::MayBlock(),
80        base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
81 
82   task_runner_->PostTaskAndReplyWithResult(
83       FROM_HERE, base::BindOnce(&Read<T>, path_),
84       base::BindOnce(&PersistentProto<T>::OnReadComplete,
85                      weak_factory_.GetWeakPtr()));
86 }
87 
88 template <class T>
~PersistentProto()89 PersistentProto<T>::~PersistentProto() {
90   if (has_value()) {
91     std::string proto_str;
92     if (!proto_->SerializeToString(&proto_str)) {
93       OnWriteComplete(WriteStatus::kSerializationError);
94     }
95     Write(path_, proto_str);
96   }
97 }
98 
99 template <class T>
OnReadComplete(std::pair<ReadStatus,std::unique_ptr<T>> result)100 void PersistentProto<T>::OnReadComplete(
101     std::pair<ReadStatus, std::unique_ptr<T>> result) {
102   if (result.first == ReadStatus::kOk) {
103     proto_ = std::move(result.second);
104   } else {
105     proto_ = std::make_unique<T>();
106     QueueWrite();
107   }
108 
109   if (purge_after_reading_) {
110     proto_.reset();
111     proto_ = std::make_unique<T>();
112     StartWrite();
113     purge_after_reading_ = false;
114   }
115 
116   std::move(on_read_).Run(result.first);
117 }
118 
119 template <class T>
QueueWrite()120 void PersistentProto<T>::QueueWrite() {
121   DCHECK(proto_);
122   if (!proto_) {
123     return;
124   }
125 
126   // If a save is already queued, do nothing.
127   if (write_is_queued_) {
128     return;
129   }
130   write_is_queued_ = true;
131 
132   base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
133       FROM_HERE,
134       base::BindOnce(&PersistentProto<T>::OnQueueWrite,
135                      weak_factory_.GetWeakPtr()),
136       write_delay_);
137 }
138 
139 template <class T>
OnQueueWrite()140 void PersistentProto<T>::OnQueueWrite() {
141   // Reset the queued flag before posting the task. Last-moment updates to
142   // |proto_| will post another task to write the proto, avoiding race
143   // conditions.
144   write_is_queued_ = false;
145   StartWrite();
146 }
147 
148 template <class T>
StartWrite()149 void PersistentProto<T>::StartWrite() {
150   DCHECK(proto_);
151   if (!proto_) {
152     return;
153   }
154 
155   // Serialize the proto outside of the posted task, because otherwise we need
156   // to pass a proto pointer into the task. This causes a rare race condition
157   // during destruction where the proto can be destroyed before serialization,
158   // causing a crash.
159   std::string proto_str;
160   if (!proto_->SerializeToString(&proto_str)) {
161     OnWriteComplete(WriteStatus::kSerializationError);
162   }
163 
164   // The SequentialTaskRunner ensures the writes won't trip over each other, so
165   // we can schedule without checking whether another write is currently active.
166   task_runner_->PostTaskAndReplyWithResult(
167       FROM_HERE, base::BindOnce(&Write, path_, proto_str),
168       base::BindOnce(&PersistentProto<T>::OnWriteComplete,
169                      weak_factory_.GetWeakPtr()));
170 }
171 
172 template <class T>
OnWriteComplete(const WriteStatus status)173 void PersistentProto<T>::OnWriteComplete(const WriteStatus status) {
174   on_write_.Run(status);
175 }
176 
177 template <class T>
Purge()178 void PersistentProto<T>::Purge() {
179   if (proto_) {
180     proto_.reset();
181     proto_ = std::make_unique<T>();
182     StartWrite();
183   } else {
184     purge_after_reading_ = true;
185   }
186 }
187 
188 // A list of all types that the PersistentProto can be used with.
189 template class PersistentProto<EventsProto>;
190 template class PersistentProto<KeyDataProto>;
191 template class PersistentProto<KeyProto>;
192 
193 }  // namespace structured
194 }  // namespace metrics
195