Skip to content

Commit 7ed68e0

Browse files
yaoxiachromiumChromium LUCI CQ
authored and
Chromium LUCI CQ
committed
[Topics] Implement BrowsingTopicsSiteDataStorage database
Explainer: https://github.com/jkarlin/topics Design doc: https://docs.google.com/document/d/12UEo6PgeySUgEpkaAoawPjhjR0GSz-XhSOXSmrMxV6U PoC CL: https://chromium-review.googlesource.com/c/chromium/src/+/3416117 Bug: 1294456 Change-Id: I6ab632d10147737a6560539a6a4698e87013dddc Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3456782 Reviewed-by: Josh Karlin <[email protected]> Reviewed-by: Marijn Kruisselbrink <[email protected]> Reviewed-by: Weilun Shi <[email protected]> Reviewed-by: Ted Choc <[email protected]> Reviewed-by: Daniel Cheng <[email protected]> Reviewed-by: Avi Drissman <[email protected]> Commit-Queue: Yao Xiao <[email protected]> Cr-Commit-Position: refs/heads/main@{#974934}
1 parent c72c93d commit 7ed68e0

File tree

17 files changed

+1036
-0
lines changed

17 files changed

+1036
-0
lines changed

components/browsing_topics/OWNERS

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright 2022 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+
component("common") {
6+
output_name = "browsing_topics_common"
7+
8+
defines = [ "IS_BROWSING_TOPICS_COMMON_IMPL" ]
9+
10+
sources = [
11+
"common_types.cc",
12+
"common_types.h",
13+
]
14+
15+
deps = [ "//base" ]
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2022 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 "components/browsing_topics/common/common_types.h"
6+
7+
namespace browsing_topics {
8+
9+
ApiUsageContextQueryResult::ApiUsageContextQueryResult() = default;
10+
11+
ApiUsageContextQueryResult::ApiUsageContextQueryResult(
12+
std::vector<ApiUsageContext> api_usage_contexts)
13+
: success(true), api_usage_contexts(std::move(api_usage_contexts)) {}
14+
15+
ApiUsageContextQueryResult::ApiUsageContextQueryResult(
16+
ApiUsageContextQueryResult&& other) = default;
17+
18+
ApiUsageContextQueryResult& ApiUsageContextQueryResult::operator=(
19+
ApiUsageContextQueryResult&& other) = default;
20+
21+
ApiUsageContextQueryResult::~ApiUsageContextQueryResult() = default;
22+
23+
} // namespace browsing_topics
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2022 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+
#ifndef COMPONENTS_BROWSING_TOPICS_COMMON_COMMON_TYPES_H_
6+
#define COMPONENTS_BROWSING_TOPICS_COMMON_COMMON_TYPES_H_
7+
8+
#include <vector>
9+
10+
#include "base/component_export.h"
11+
#include "base/time/time.h"
12+
#include "base/types/strong_alias.h"
13+
14+
namespace browsing_topics {
15+
16+
using HashedHost = base::StrongAlias<class HashedHostTag, int64_t>;
17+
using HashedDomain = base::StrongAlias<class HashedHostTag, int64_t>;
18+
19+
struct COMPONENT_EXPORT(BROWSING_TOPICS_COMMON) ApiUsageContext {
20+
HashedDomain hashed_context_domain;
21+
HashedHost hashed_top_host;
22+
base::Time time;
23+
};
24+
25+
struct COMPONENT_EXPORT(BROWSING_TOPICS_COMMON) ApiUsageContextQueryResult {
26+
ApiUsageContextQueryResult();
27+
explicit ApiUsageContextQueryResult(
28+
std::vector<ApiUsageContext> api_usage_contexts);
29+
30+
ApiUsageContextQueryResult(const ApiUsageContextQueryResult&) = delete;
31+
ApiUsageContextQueryResult& operator=(const ApiUsageContextQueryResult&) =
32+
delete;
33+
34+
ApiUsageContextQueryResult(ApiUsageContextQueryResult&&);
35+
ApiUsageContextQueryResult& operator=(ApiUsageContextQueryResult&&);
36+
37+
~ApiUsageContextQueryResult();
38+
39+
bool success = false;
40+
41+
std::vector<ApiUsageContext> api_usage_contexts;
42+
};
43+
44+
} // namespace browsing_topics
45+
46+
#endif // COMPONENTS_BROWSING_TOPICS_COMMON_COMMON_TYPES_H_

content/DEPS

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ include_rules = [
2525
# as autofill or extensions, and chrome implementation details such as
2626
# settings, packaging details, installation or crash reporting.
2727

28+
"+components/browsing_topics/common",
2829
"+components/memory_pressure",
2930
"+components/power_scheduler",
3031
"+components/services/filesystem",

content/browser/BUILD.gn

+3
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ source_set("browser") {
6262
"//cc/mojo_embedder",
6363
"//cc/paint",
6464
"//components/back_forward_cache:enum",
65+
"//components/browsing_topics/common:common",
6566
"//components/cbor",
6667
"//components/discardable_memory/common",
6768
"//components/discardable_memory/service",
@@ -600,6 +601,8 @@ source_set("browser") {
600601
"browsing_data/storage_partition_code_cache_data_remover.h",
601602
"browsing_instance.cc",
602603
"browsing_instance.h",
604+
"browsing_topics/browsing_topics_site_data_storage.cc",
605+
"browsing_topics/browsing_topics_site_data_storage.h",
603606
"buckets/bucket_context.cc",
604607
"buckets/bucket_context.h",
605608
"buckets/bucket_host.cc",
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
file://components/browsing_topics/OWNERS
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
// Copyright 2022 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 "content/browser/browsing_topics/browsing_topics_site_data_storage.h"
6+
7+
#include "base/files/file_util.h"
8+
#include "base/metrics/histogram_functions.h"
9+
#include "sql/database.h"
10+
#include "sql/recovery.h"
11+
#include "sql/statement.h"
12+
#include "sql/transaction.h"
13+
#include "third_party/blink/public/common/features.h"
14+
15+
namespace content {
16+
17+
namespace {
18+
19+
// Version number of the database.
20+
const int kCurrentVersionNumber = 1;
21+
22+
void RecordInitializationStatus(bool successful) {
23+
base::UmaHistogramBoolean("BrowsingTopics.SiteDataStorage.InitStatus",
24+
successful);
25+
}
26+
27+
} // namespace
28+
29+
BrowsingTopicsSiteDataStorage::BrowsingTopicsSiteDataStorage(
30+
const base::FilePath& path_to_database)
31+
: path_to_database_(path_to_database) {
32+
DETACH_FROM_SEQUENCE(sequence_checker_);
33+
}
34+
35+
BrowsingTopicsSiteDataStorage::~BrowsingTopicsSiteDataStorage() {
36+
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
37+
}
38+
39+
void BrowsingTopicsSiteDataStorage::ExpireDataBefore(base::Time end_time) {
40+
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
41+
42+
if (!LazyInit())
43+
return;
44+
45+
static constexpr char kDeleteApiUsageSql[] =
46+
// clang-format off
47+
"DELETE FROM browsing_topics_api_usages "
48+
"WHERE last_usage_time < ?";
49+
// clang-format on
50+
51+
sql::Statement delete_api_usage_statement(
52+
db_->GetCachedStatement(SQL_FROM_HERE, kDeleteApiUsageSql));
53+
delete_api_usage_statement.BindTime(0, end_time);
54+
55+
delete_api_usage_statement.Run();
56+
}
57+
58+
browsing_topics::ApiUsageContextQueryResult
59+
BrowsingTopicsSiteDataStorage::GetBrowsingTopicsApiUsage(base::Time begin_time,
60+
base::Time end_time) {
61+
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
62+
63+
if (!LazyInit())
64+
return {};
65+
66+
// Expire data before `begin_time`, as they are no longer needed.
67+
ExpireDataBefore(begin_time);
68+
69+
static constexpr char kGetApiUsageSql[] =
70+
// clang-format off
71+
"SELECT hashed_context_domain,hashed_top_host,last_usage_time "
72+
"FROM browsing_topics_api_usages "
73+
"WHERE last_usage_time>=? AND last_usage_time<? "
74+
"ORDER BY last_usage_time DESC "
75+
"LIMIT ?";
76+
// clang-format on
77+
78+
sql::Statement statement(
79+
db_->GetCachedStatement(SQL_FROM_HERE, kGetApiUsageSql));
80+
81+
statement.BindTime(0, begin_time);
82+
statement.BindTime(1, end_time);
83+
statement.BindInt(
84+
2,
85+
blink::features::
86+
kBrowsingTopicsMaxNumberOfApiUsageContextEntriesToLoadPerEpoch.Get());
87+
88+
std::vector<browsing_topics::ApiUsageContext> contexts;
89+
while (statement.Step()) {
90+
browsing_topics::ApiUsageContext usage_context;
91+
usage_context.hashed_context_domain =
92+
browsing_topics::HashedDomain(statement.ColumnInt64(0));
93+
usage_context.hashed_top_host =
94+
browsing_topics::HashedHost(statement.ColumnInt64(1));
95+
usage_context.time = statement.ColumnTime(2);
96+
97+
contexts.push_back(std::move(usage_context));
98+
}
99+
100+
if (!statement.Succeeded())
101+
return {};
102+
103+
return browsing_topics::ApiUsageContextQueryResult(std::move(contexts));
104+
}
105+
106+
void BrowsingTopicsSiteDataStorage::OnBrowsingTopicsApiUsed(
107+
const browsing_topics::HashedHost& hashed_top_host,
108+
const base::flat_set<browsing_topics::HashedDomain>&
109+
hashed_context_domains) {
110+
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
111+
112+
if (!LazyInit())
113+
return;
114+
115+
sql::Transaction transaction(db_.get());
116+
if (!transaction.Begin())
117+
return;
118+
119+
base::Time current_time = base::Time::Now();
120+
121+
for (const browsing_topics::HashedDomain& hashed_context_domain :
122+
hashed_context_domains) {
123+
static constexpr char kInsertApiUsageSql[] =
124+
// clang-format off
125+
"INSERT OR REPLACE INTO browsing_topics_api_usages "
126+
"(hashed_context_domain,hashed_top_host,last_usage_time) "
127+
"VALUES (?,?,?)";
128+
// clang-format on
129+
130+
sql::Statement insert_api_usage_statement(
131+
db_->GetCachedStatement(SQL_FROM_HERE, kInsertApiUsageSql));
132+
insert_api_usage_statement.BindInt64(0, hashed_context_domain.value());
133+
insert_api_usage_statement.BindInt64(1, hashed_top_host.value());
134+
insert_api_usage_statement.BindTime(2, current_time);
135+
136+
if (!insert_api_usage_statement.Run())
137+
return;
138+
}
139+
140+
transaction.Commit();
141+
}
142+
143+
bool BrowsingTopicsSiteDataStorage::LazyInit() {
144+
if (db_init_status_ != InitStatus::kUnattempted)
145+
return db_init_status_ == InitStatus::kSuccess;
146+
147+
db_ = std::make_unique<sql::Database>(sql::DatabaseOptions{
148+
.exclusive_locking = true, .page_size = 4096, .cache_size = 32});
149+
db_->set_histogram_tag("BrowsingTopics");
150+
151+
// base::Unretained is safe here because this BrowsingTopicsSiteDataStorage
152+
// owns the sql::Database instance that stores and uses the callback. So,
153+
// `this` is guaranteed to outlive the callback.
154+
db_->set_error_callback(
155+
base::BindRepeating(&BrowsingTopicsSiteDataStorage::DatabaseErrorCallback,
156+
base::Unretained(this)));
157+
158+
if (!db_->Open(path_to_database_)) {
159+
HandleInitializationFailure();
160+
return false;
161+
}
162+
163+
// TODO(yaoxia): measure metrics for the DB file size to have some idea if it
164+
// gets too big.
165+
166+
if (!InitializeTables()) {
167+
HandleInitializationFailure();
168+
return false;
169+
}
170+
171+
db_init_status_ = InitStatus::kSuccess;
172+
RecordInitializationStatus(true);
173+
return true;
174+
}
175+
176+
bool BrowsingTopicsSiteDataStorage::InitializeTables() {
177+
sql::Transaction transaction(db_.get());
178+
if (!transaction.Begin())
179+
return false;
180+
181+
if (!meta_table_.Init(db_.get(), kCurrentVersionNumber,
182+
kCurrentVersionNumber)) {
183+
return false;
184+
}
185+
186+
if (!CreateSchema())
187+
return false;
188+
189+
// This is the first code version. No database version is expected to be
190+
// smaller. Fail when this happens.
191+
if (meta_table_.GetVersionNumber() < kCurrentVersionNumber)
192+
return false;
193+
194+
if (!transaction.Commit())
195+
return false;
196+
197+
// This is possible with code reverts. The DB will never work until Chrome
198+
// is re-upgraded. Assume the user will continue using this Chrome version
199+
// and raze the DB to get the feature working.
200+
if (meta_table_.GetVersionNumber() > kCurrentVersionNumber) {
201+
db_->Raze();
202+
meta_table_.Reset();
203+
return InitializeTables();
204+
}
205+
206+
return true;
207+
}
208+
209+
bool BrowsingTopicsSiteDataStorage::CreateSchema() {
210+
static constexpr char kBrowsingTopicsApiUsagesTableSql[] =
211+
// clang-format off
212+
"CREATE TABLE IF NOT EXISTS browsing_topics_api_usages("
213+
"hashed_context_domain INTEGER NOT NULL,"
214+
"hashed_top_host INTEGER NOT NULL,"
215+
"last_usage_time INTEGER NOT NULL,"
216+
"PRIMARY KEY (hashed_context_domain,hashed_top_host))";
217+
// clang-format on
218+
if (!db_->Execute(kBrowsingTopicsApiUsagesTableSql))
219+
return false;
220+
221+
static constexpr char kLastUsageTimeIndexSql[] =
222+
// clang-format off
223+
"CREATE INDEX IF NOT EXISTS last_usage_time_idx "
224+
"ON browsing_topics_api_usages(last_usage_time)";
225+
// clang-format on
226+
if (!db_->Execute(kLastUsageTimeIndexSql))
227+
return false;
228+
229+
return true;
230+
}
231+
232+
void BrowsingTopicsSiteDataStorage::HandleInitializationFailure() {
233+
db_.reset();
234+
db_init_status_ = InitStatus::kFailure;
235+
RecordInitializationStatus(false);
236+
}
237+
238+
void BrowsingTopicsSiteDataStorage::DatabaseErrorCallback(
239+
int extended_error,
240+
sql::Statement* stmt) {
241+
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
242+
// Attempt to recover a corrupt database.
243+
if (sql::Recovery::ShouldRecover(extended_error)) {
244+
// Prevent reentrant calls.
245+
db_->reset_error_callback();
246+
247+
// After this call, the |db_| handle is poisoned so that future calls will
248+
// return errors until the handle is re-opened.
249+
sql::Recovery::RecoverDatabaseWithMetaVersion(db_.get(), path_to_database_);
250+
251+
// The DLOG(FATAL) below is intended to draw immediate attention to errors
252+
// in newly-written code. Database corruption is generally a result of OS or
253+
// hardware issues, not coding errors at the client level, so displaying the
254+
// error would probably lead to confusion. The ignored call signals the
255+
// test-expectation framework that the error was handled.
256+
std::ignore = sql::Database::IsExpectedSqliteError(extended_error);
257+
return;
258+
}
259+
260+
// The default handling is to assert on debug and to ignore on release.
261+
if (!sql::Database::IsExpectedSqliteError(extended_error))
262+
DLOG(FATAL) << db_->GetErrorMessage();
263+
264+
// Consider the database closed if we did not attempt to recover so we did not
265+
// produce further errors.
266+
db_init_status_ = InitStatus::kFailure;
267+
}
268+
269+
} // namespace content

0 commit comments

Comments
 (0)