xref: /aosp_15_r20/external/cronet/components/metrics/structured/lib/persistent_proto_internal.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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/logging.h"
16 #include "base/rand_util.h"
17 #include "base/task/bind_post_task.h"
18 #include "base/task/sequenced_task_runner.h"
19 #include "base/task/task_traits.h"
20 #include "base/task/thread_pool.h"
21 
22 namespace metrics::structured::internal {
23 
24 namespace {
25 
26 // Attempts to read from |filepath| and returns a string with the file content
27 // if successful.
Read(const base::FilePath & filepath)28 base::expected<std::string, ReadStatus> Read(const base::FilePath& filepath) {
29   if (!base::PathExists(filepath)) {
30     return base::unexpected(ReadStatus::kMissing);
31   }
32 
33   std::string proto_str;
34   if (!base::ReadFileToString(filepath, &proto_str)) {
35     return base::unexpected(ReadStatus::kReadError);
36   }
37 
38   return base::ok(std::move(proto_str));
39 }
40 
41 }  // namespace
42 
PersistentProtoInternal(const base::FilePath & path,base::TimeDelta write_delay,PersistentProtoInternal::ReadCallback on_read,PersistentProtoInternal::WriteCallback on_write)43 PersistentProtoInternal::PersistentProtoInternal(
44     const base::FilePath& path,
45     base::TimeDelta write_delay,
46     PersistentProtoInternal::ReadCallback on_read,
47     PersistentProtoInternal::WriteCallback on_write)
48     : on_read_(std::move(on_read)),
49       on_write_(std::move(on_write)),
50       task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
51           {base::TaskPriority::BEST_EFFORT, base::MayBlock(),
52            base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
53       proto_file_(std::make_unique<base::ImportantFileWriter>(
54           path,
55           task_runner_,
56           write_delay,
57           "StructuredMetricsPersistentProto")) {
58   task_runner_->PostTaskAndReplyWithResult(
59       FROM_HERE, base::BindOnce(&Read, proto_file_->path()),
60       base::BindOnce(&PersistentProtoInternal::OnReadComplete,
61                      weak_factory_.GetWeakPtr()));
62 }
63 
64 PersistentProtoInternal::~PersistentProtoInternal() = default;
65 
OnReadComplete(base::expected<std::string,ReadStatus> read_status)66 void PersistentProtoInternal::OnReadComplete(
67     base::expected<std::string, ReadStatus> read_status) {
68   ReadStatus status;
69 
70   proto_ = GetProto();
71 
72   if (read_status.has_value()) {
73     status = ReadStatus::kOk;
74 
75     // Parses the value of |read_status| into |proto_| but attempts to preserve
76     // any existing content. Optional values will be overwritten and repeated
77     // fields will be appended with new values.
78     if (!proto_->MergeFromString(read_status.value())) {
79       status = ReadStatus::kParseError;
80       QueueWrite();
81     }
82   } else {
83     status = read_status.error();
84   }
85 
86   // If there was an error, write an empty proto.
87   if (status != ReadStatus::kOk) {
88     QueueWrite();
89   }
90 
91   // Purge the read proto if |purge_after_reading_|.
92   if (purge_after_reading_) {
93     proto_->Clear();
94     purge_after_reading_ = false;
95   }
96 
97   std::move(on_read_).Run(std::move(status));
98 }
99 
QueueWrite()100 void PersistentProtoInternal::QueueWrite() {
101   // Read |updating_path_| to check if we are actively updating the path of this
102   // proto.
103   if (updating_path_.load()) {
104     return;
105   }
106 
107   // |proto_| will be null if OnReadComplete() has not finished executing. It is
108   // up to the user to verify that OnReadComplete() has finished with callback
109   // |on_read_| before calling QueueWrite().
110   CHECK(proto_);
111   proto_file_->ScheduleWrite(this);
112 }
113 
OnWriteAttempt(bool write_successful)114 void PersistentProtoInternal::OnWriteAttempt(bool write_successful) {
115   if (write_successful) {
116     OnWriteComplete(WriteStatus::kOk);
117   } else {
118     OnWriteComplete(WriteStatus::kWriteError);
119   }
120 }
121 
OnWriteComplete(const WriteStatus status)122 void PersistentProtoInternal::OnWriteComplete(const WriteStatus status) {
123   on_write_.Run(status);
124 }
125 
Purge()126 void PersistentProtoInternal::Purge() {
127   if (proto_) {
128     proto_->Clear();
129     QueueWrite();
130   } else {
131     purge_after_reading_ = true;
132   }
133 }
134 
SerializeData()135 std::optional<std::string> PersistentProtoInternal::SerializeData() {
136   std::string proto_str;
137   if (!proto_->SerializeToString(&proto_str)) {
138     OnWriteComplete(WriteStatus::kSerializationError);
139     return std::nullopt;
140   }
141   proto_file_->RegisterOnNextWriteCallbacks(
142       base::BindOnce(base::IgnoreResult(&base::CreateDirectory),
143                      proto_file_->path().DirName()),
144       base::BindPostTask(
145           base::SequencedTaskRunner::GetCurrentDefault(),
146           base::BindOnce(&PersistentProtoInternal::OnWriteAttempt,
147                          weak_factory_.GetWeakPtr())));
148   return proto_str;
149 }
150 
StartWriteForTesting()151 void PersistentProtoInternal::StartWriteForTesting() {
152   proto_file_->ScheduleWrite(this);
153   proto_file_->DoScheduledWrite();
154 }
155 
UpdatePath(const base::FilePath & path,ReadCallback on_read,bool remove_existing)156 void PersistentProtoInternal::UpdatePath(const base::FilePath& path,
157                                          ReadCallback on_read,
158                                          bool remove_existing) {
159   updating_path_.store(true);
160 
161   // Clean up the state of the current |proto_file_|.
162   if (proto_file_->HasPendingWrite()) {
163     proto_file_->DoScheduledWrite();
164   }
165 
166   // If the previous file should be cleaned up then schedule the cleanup on
167   // separate thread.
168   if (remove_existing) {
169     task_runner_->PostTask(FROM_HERE,
170                            base::BindOnce(base::IgnoreResult(&base::DeleteFile),
171                                           proto_file_->path()));
172   }
173 
174   // Overwrite the ImportantFileWriter a new one to the new path.
175   proto_file_ = std::make_unique<base::ImportantFileWriter>(
176       path, task_runner_, proto_file_->commit_interval(),
177       "StructuredMetricsPersistentProto");
178 
179   on_read_ = std::move(on_read);
180   task_runner_->PostTaskAndReplyWithResult(
181       FROM_HERE, base::BindOnce(&Read, proto_file_->path()),
182       base::BindOnce(&PersistentProtoInternal::OnReadComplete,
183                      weak_factory_.GetWeakPtr()));
184 
185   updating_path_.store(false);
186 
187   // Write the content of the proto back to the path in case it has changed. If
188   // an error occurs while reading |path| then 2 write can occur.
189   QueueWrite();
190 }
191 
DeallocProto()192 void PersistentProtoInternal::DeallocProto() {
193   FlushQueuedWrites();
194   proto_ = nullptr;
195 }
196 
FlushQueuedWrites()197 void PersistentProtoInternal::FlushQueuedWrites() {
198   if (proto_file_->HasPendingWrite()) {
199     proto_file_->DoScheduledWrite();
200   }
201 }
202 
203 }  // namespace metrics::structured::internal
204