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