• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 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/lib/persistent_proto_internal.h"
6 
7 #include <atomic>
8 #include <memory>
9 #include <string>
10 #include <utility>
11 
12 #include "base/files/file_util.h"
13 #include "base/files/important_file_writer.h"
14 #include "base/functional/bind.h"
15 #include "base/rand_util.h"
16 #include "base/task/bind_post_task.h"
17 #include "base/task/sequenced_task_runner.h"
18 #include "base/task/task_traits.h"
19 #include "base/task/thread_pool.h"
20 
21 namespace metrics::structured::internal {
22 
23 namespace {
24 
25 // Attempts to read from |filepath| and returns a string with the file content
26 // if successful.
Read(const base::FilePath & filepath)27 base::expected<std::string, ReadStatus> Read(const base::FilePath& filepath) {
28   if (!base::PathExists(filepath)) {
29     return base::unexpected(ReadStatus::kMissing);
30   }
31 
32   std::string proto_str;
33   if (!base::ReadFileToString(filepath, &proto_str)) {
34     return base::unexpected(ReadStatus::kReadError);
35   }
36 
37   return base::ok(std::move(proto_str));
38 }
39 
40 }  // namespace
41 
PersistentProtoInternal(const base::FilePath & path,base::TimeDelta write_delay,PersistentProtoInternal::ReadCallback on_read,PersistentProtoInternal::WriteCallback on_write)42 PersistentProtoInternal::PersistentProtoInternal(
43     const base::FilePath& path,
44     base::TimeDelta write_delay,
45     PersistentProtoInternal::ReadCallback on_read,
46     PersistentProtoInternal::WriteCallback on_write)
47     : on_write_(std::move(on_write)),
48       task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
49           {base::TaskPriority::BEST_EFFORT, base::MayBlock(),
50            base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
51       proto_file_(std::make_unique<base::ImportantFileWriter>(
52           path,
53           task_runner_,
54           write_delay,
55           "StructuredMetricsPersistentProto")) {
56   task_runner_->PostTaskAndReplyWithResult(
57       FROM_HERE, base::BindOnce(&Read, proto_file_->path()),
58       base::BindOnce(&PersistentProtoInternal::OnReadComplete,
59                      weak_factory_.GetWeakPtr(), std::move(on_read)));
60 }
61 
62 PersistentProtoInternal::~PersistentProtoInternal() = default;
63 
OnReadComplete(ReadCallback callback,base::expected<std::string,ReadStatus> read_status)64 void PersistentProtoInternal::OnReadComplete(
65     ReadCallback callback,
66     base::expected<std::string, ReadStatus> read_status) {
67   ReadStatus status;
68 
69   // If the path was updated then we may not need to update the pointer.
70   if (proto_ == nullptr) {
71     proto_ = GetProto();
72   }
73 
74   if (read_status.has_value()) {
75     status = ReadStatus::kOk;
76 
77     // Parses the value of |read_status| into |proto_| but attempts to preserve
78     // any existing content. Optional values will be overwritten and repeated
79     // fields will be appended with new values.
80     if (!proto_->MergeFromString(read_status.value())) {
81       status = ReadStatus::kParseError;
82       QueueWrite();
83     }
84   } else {
85     status = read_status.error();
86   }
87 
88   // If there was an error, write an empty proto.
89   if (status != ReadStatus::kOk) {
90     QueueWrite();
91   }
92 
93   // Purge the read proto if |purge_after_reading_|.
94   if (purge_after_reading_) {
95     Purge();
96     purge_after_reading_ = false;
97   }
98 
99   std::move(callback).Run(std::move(status));
100 }
101 
QueueWrite()102 void PersistentProtoInternal::QueueWrite() {
103   // Read |updating_path_| to check if we are actively updating the path of this
104   // proto.
105   if (updating_path_.load()) {
106     return;
107   }
108 
109   // |proto_| will be null if OnReadComplete() has not finished executing. It is
110   // up to the user to verify that OnReadComplete() has finished with callback
111   // |on_read_| before calling QueueWrite().
112   CHECK(proto_);
113 
114   // Serialize the proto into a buffer for write to occur. Because the IO
115   // happens on a separate sequence, serialization must happen on this sequence
116   // since PersistentProto is not thread-safe.
117   SerializeProtoForWrite();
118 
119   proto_file_->ScheduleWrite(this);
120 }
121 
OnWriteAttempt(bool write_successful)122 void PersistentProtoInternal::OnWriteAttempt(bool write_successful) {
123   write_buffer_.clear();
124 
125   if (write_successful) {
126     OnWriteComplete(WriteStatus::kOk);
127   } else {
128     OnWriteComplete(WriteStatus::kWriteError);
129   }
130 }
131 
OnWriteComplete(const WriteStatus status)132 void PersistentProtoInternal::OnWriteComplete(const WriteStatus status) {
133   on_write_.Run(status);
134 }
135 
Purge()136 void PersistentProtoInternal::Purge() {
137   if (proto_) {
138     proto_->Clear();
139     QueueFileDelete();
140   } else {
141     purge_after_reading_ = true;
142   }
143 }
144 
SerializeData()145 std::optional<std::string> PersistentProtoInternal::SerializeData() {
146   proto_file_->RegisterOnNextWriteCallbacks(
147       base::BindOnce(base::IgnoreResult(&base::CreateDirectory),
148                      proto_file_->path().DirName()),
149       base::BindPostTask(
150           base::SequencedTaskRunner::GetCurrentDefault(),
151           base::BindOnce(&PersistentProtoInternal::OnWriteAttempt,
152                          weak_factory_.GetWeakPtr())));
153   return write_buffer_;
154 }
155 
StartWriteForTesting()156 void PersistentProtoInternal::StartWriteForTesting() {
157   SerializeProtoForWrite();
158 
159   proto_file_->ScheduleWrite(this);
160   proto_file_->DoScheduledWrite();
161 }
162 
UpdatePath(const base::FilePath & path,ReadCallback on_read,bool remove_existing)163 void PersistentProtoInternal::UpdatePath(const base::FilePath& path,
164                                          ReadCallback on_read,
165                                          bool remove_existing) {
166   updating_path_.store(true);
167 
168   // Clean up the state of the current |proto_file_|.
169   FlushQueuedWrites();
170 
171   // If the previous file should be cleaned up then schedule the cleanup on
172   // separate thread.
173   if (remove_existing) {
174     task_runner_->PostTask(FROM_HERE,
175                            base::BindOnce(base::IgnoreResult(&base::DeleteFile),
176                                           proto_file_->path()));
177   }
178 
179   // Overwrite the ImportantFileWriter with a new one at the new path.
180   proto_file_ = std::make_unique<base::ImportantFileWriter>(
181       path, task_runner_, proto_file_->commit_interval(),
182       "StructuredMetricsPersistentProto");
183 
184   task_runner_->PostTaskAndReplyWithResult(
185       FROM_HERE, base::BindOnce(&Read, proto_file_->path()),
186       base::BindOnce(&PersistentProtoInternal::OnReadComplete,
187                      weak_factory_.GetWeakPtr(), std::move(on_read)));
188 
189   updating_path_.store(false);
190 
191   // Write the content of the proto back to the path in case it has changed. If
192   // an error occurs while reading |path| then 2 write can occur.
193   //
194   // It is possible in tests that the profile is added before the pre-profile
195   // events have been loaded, which initializes the proto. In this case, we do
196   // not want to queue a write.
197   if (proto_) {
198     QueueWrite();
199   }
200 }
201 
DeallocProto()202 void PersistentProtoInternal::DeallocProto() {
203   FlushQueuedWrites();
204   proto_ = nullptr;
205 }
206 
QueueFileDelete()207 void PersistentProtoInternal::QueueFileDelete() {
208   task_runner_->PostTask(FROM_HERE,
209                          base::BindOnce(base::IgnoreResult(&base::DeleteFile),
210                                         proto_file_->path()));
211 }
212 
FlushQueuedWrites()213 void PersistentProtoInternal::FlushQueuedWrites() {
214   if (proto_file_->HasPendingWrite()) {
215     proto_file_->DoScheduledWrite();
216   }
217 }
218 
SerializeProtoForWrite()219 void PersistentProtoInternal::SerializeProtoForWrite() {
220   // If the write buffer is not empty, it means that a write is already in
221   // progress.
222   if (!write_buffer_.empty()) {
223     return;
224   }
225 
226   if (!proto_->SerializeToString(&write_buffer_)) {
227     write_buffer_.clear();
228     OnWriteComplete(WriteStatus::kSerializationError);
229     return;
230   }
231 }
232 
233 }  // namespace metrics::structured::internal
234