[persistent_cache]: Introduce schema versioning [chromium/src : main]

0 views
Skip to first unread message

Greg Thompson (Gerrit)

unread,
Nov 26, 2025, 10:20:31 AM11/26/25
to Olivier Li, Chromium LUCI CQ, chromium...@chromium.org
Attention needed from Olivier Li

Greg Thompson added 11 comments

Patchset-level comments
File-level comment, Patchset 17 (Latest):
Greg Thompson . resolved

partial review

File components/persistent_cache/sqlite/sqlite_backend_impl.cc
Line 42, Patchset 17 (Latest):constexpr int32_t kLatestUserVersion = 1;
Greg Thompson . unresolved

q: `Current` since it's the one that's currently being used until the next commit that changes it? i dunno...

Line 165, Patchset 17 (Latest): if (!db_->Open(database_path_)) {
Greg Thompson . unresolved

a counter-proposal in pseudocode:

```
read the fist hunk of the db file into a buffer before opening the databse
pull out the ints at offset 68[1] and offset 60[1] in the buffer
if these don't match the current version:
if not vfs_file_set_.read_only():
truncate all files -- the cache becomes a fresh cache file
else:
return error
```

i think this is less code, less overhead, and easier to reason about compared to checking for existence of the table. it also avoids the GPU case of needing to bounce back to the browser process to get a new file when there's a version change (the browser doesn't open the db today). wdyt?

1. [application id](https://sqlite.org/fileformat2.html#application_id)
2. [user version number](https://sqlite.org/fileformat2.html#user_version_number)

(i'm not sure if this is a great idea -- just tossing it out there as a possibility)

Line 176, Patchset 17 (Latest): sql::Statement get_user_version_stm =
sql::Statement(db_->GetUniqueStatement("PRAGMA user_version"));
Greg Thompson . unresolved

nit: omit `= sql::Statement` -- just `sql::Statement get_user_version_stm(db_->GetUniqueStatement("PRAGMA user_version"));`

Line 178, Patchset 17 (Latest): DCHECK(get_user_version_stm.is_valid());
Greg Thompson . unresolved

what are the failure modes? would we really want to crash a debug/DCHECK build for them?

Line 180, Patchset 17 (Latest): std::optional<int32_t> detected_user_version;
Greg Thompson . unresolved

nit: move down after `Step()` and return

Line 184, Patchset 17 (Latest): detected_user_version.emplace(get_user_version_stm.ColumnInt(0));
Greg Thompson . unresolved

we don't need `optional` on this int, do we?

Line 200, Patchset 17 (Latest): {"PRAGMA user_version=", base::NumberToString(user_version_)}))) {
Greg Thompson . unresolved

even though this is in a transaction, shall we move this down after table creation? i think this is more natural, but it's your call.

Line 206, Patchset 17 (Latest): const std::string create_table_sql = base::StringPrintf(
Greg Thompson . unresolved

`StringPrintf` is a horribly expensive beast. :-) wdyt about either putting this statement up at the top of the file next to the table name's definition so that they can easily be audited? or do some light preprocessor juju to mash things together? i'm not concerned about leaving "entries" in this string as it was. i think it will take a developer 5 seconds to realize their mistake if they're changing the name of the table and miss one of the places.

File components/persistent_cache/sqlite/sqlite_backend_unittest.cc
Line 33, Patchset 17 (Latest): // Grab another pending backend to be able to re-open the database.
ASSERT_OK_AND_ASSIGN(
auto other_pending_backend,
backend_storage_->MakePendingBackend(db_path, false, false));
Greg Thompson . unresolved

one must use `ShareReadWriteConnection` or `ShareReadOnlyConnection` to get a new `PendingBackend` to connect to an existing backend. this is the only way to have the shared lock be available for all connections. as it is now in this test, each pending backend has its own shared lock over the same files, which is dangerous.

please refactor to either use `MakePendingBackend` a second time only after destroying the first `Backend` (so that all handles are closed), or `Bind` the first `PendingBackend` and then use `Share...Connection` to get a second `PendingBackend` to the same storage.

even though this is a test, and you might be tempted to argue "it's fine here 'cause we're not using the lock", there's still a risk of someone seeing this and trying to use this pattern in production.

Line 62, Patchset 17 (Latest): kInvalidVersion);
Greg Thompson . unresolved

nit: instead of testing by changing what is considered the "current" version (which requires adding a data member to each instance), wdyt about modifying a file on-disk to change its version? could either do it via SQL statements or just by flipping bits at offsets 60-63.

Open in Gerrit

Related details

Attention is currently required from:
  • Olivier Li
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: Id160d9b94baff3fd64bc564b12fcd60024542d6a
Gerrit-Change-Number: 7148789
Gerrit-PatchSet: 17
Gerrit-Owner: Olivier Li <oliv...@chromium.org>
Gerrit-Reviewer: Greg Thompson <g...@chromium.org>
Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
Gerrit-Attention: Olivier Li <oliv...@chromium.org>
Gerrit-Comment-Date: Wed, 26 Nov 2025 15:20:12 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
satisfied_requirement
unsatisfied_requirement
open
diffy

Olivier Li (Gerrit)

unread,
Nov 27, 2025, 3:04:49 PM11/27/25
to Greg Thompson, Chromium LUCI CQ, chromium...@chromium.org
Attention needed from Greg Thompson

Olivier Li added 10 comments

File components/persistent_cache/sqlite/sqlite_backend_impl.cc
Line 42, Patchset 17:constexpr int32_t kLatestUserVersion = 1;
Greg Thompson . resolved

q: `Current` since it's the one that's currently being used until the next commit that changes it? i dunno...

Olivier Li

Sure current sounds good 😊

Line 165, Patchset 17: if (!db_->Open(database_path_)) {
Greg Thompson . unresolved

a counter-proposal in pseudocode:

```
read the fist hunk of the db file into a buffer before opening the databse
pull out the ints at offset 68[1] and offset 60[1] in the buffer
if these don't match the current version:
if not vfs_file_set_.read_only():
truncate all files -- the cache becomes a fresh cache file
else:
return error
```

i think this is less code, less overhead, and easier to reason about compared to checking for existence of the table. it also avoids the GPU case of needing to bounce back to the browser process to get a new file when there's a version change (the browser doesn't open the db today). wdyt?

1. [application id](https://sqlite.org/fileformat2.html#application_id)
2. [user version number](https://sqlite.org/fileformat2.html#user_version_number)

(i'm not sure if this is a great idea -- just tossing it out there as a possibility)

Olivier Li

I think that reading the file number directly or truncating adds complications because it probably needs to happen under the lock so the file doesn't change in a non-thread-safe way? It's always going to be safe for exclusive connections but it feels maybe a bit clunky to have some ReadWrite backends feel confident to truncate and not others.

Local debugging shows me that we take the SHARED lock to execute the `get_user_version` statement. So I think that we could issue the truncate if we took the EXCLUSIVE lock first which can be done manually through SanboxedFile.

Alternatively we could also `DROP TABLE` which would implicitly take the lock on setups that require it without changing much in the case of exclusive connections? I quite like the simplicity of truncating the file though.

I've modified the current implementation of the CL to address some of the points raised [simplicity, speed] and once we figure out the lock situation I can have a go at adding the local handling of version mismatch.

Line 176, Patchset 17: sql::Statement get_user_version_stm =

sql::Statement(db_->GetUniqueStatement("PRAGMA user_version"));
Greg Thompson . resolved

nit: omit `= sql::Statement` -- just `sql::Statement get_user_version_stm(db_->GetUniqueStatement("PRAGMA user_version"));`

Olivier Li

Right! I've changed another instance of this in FindImpl() while I was there.

Line 178, Patchset 17: DCHECK(get_user_version_stm.is_valid());
Greg Thompson . resolved

what are the failure modes? would we really want to crash a debug/DCHECK build for them?

Olivier Li

To be completely honest I've been copy pasting that check from other parts of the code without thinking about it.

There's actually essentially zero chance for such a simple statement to spontaneously beome invalid and even then I'm not sure fully crashing is the way to go.

I've removed the DCHECK. It could be worth it to revesit the other instances of this check in the code.

Line 180, Patchset 17: std::optional<int32_t> detected_user_version;
Greg Thompson . resolved

nit: move down after `Step()` and return

Olivier Li

Right. I'm not sure why I was creating the variable in advance at all.

Line 184, Patchset 17: detected_user_version.emplace(get_user_version_stm.ColumnInt(0));
Greg Thompson . resolved

we don't need `optional` on this int, do we?

Olivier Li

As commented elsewhere I'm not sure what I was thinking there. There's no reason to not just initalize with the value from the query.

Line 200, Patchset 17: {"PRAGMA user_version=", base::NumberToString(user_version_)}))) {
Greg Thompson . resolved

even though this is in a transaction, shall we move this down after table creation? i think this is more natural, but it's your call.

Olivier Li

SGTM.

Line 206, Patchset 17: const std::string create_table_sql = base::StringPrintf(
Greg Thompson . resolved

`StringPrintf` is a horribly expensive beast. :-) wdyt about either putting this statement up at the top of the file next to the table name's definition so that they can easily be audited? or do some light preprocessor juju to mash things together? i'm not concerned about leaving "entries" in this string as it was. i think it will take a developer 5 seconds to realize their mistake if they're changing the name of the table and miss one of the places.

Olivier Li

Yeah good points 0_o. Getting rid of the StringPrintf is actually the only thing I can change in this CL to materially affect the OpenClose perftest!

Since I've stopped relying on querying for the presence of the table this has just gone away naturally 😊

File components/persistent_cache/sqlite/sqlite_backend_unittest.cc
Line 33, Patchset 17: // Grab another pending backend to be able to re-open the database.

ASSERT_OK_AND_ASSIGN(
auto other_pending_backend,
backend_storage_->MakePendingBackend(db_path, false, false));
Greg Thompson . resolved

one must use `ShareReadWriteConnection` or `ShareReadOnlyConnection` to get a new `PendingBackend` to connect to an existing backend. this is the only way to have the shared lock be available for all connections. as it is now in this test, each pending backend has its own shared lock over the same files, which is dangerous.

please refactor to either use `MakePendingBackend` a second time only after destroying the first `Backend` (so that all handles are closed), or `Bind` the first `PendingBackend` and then use `Share...Connection` to get a second `PendingBackend` to the same storage.

even though this is a test, and you might be tempted to argue "it's fine here 'cause we're not using the lock", there's still a risk of someone seeing this and trying to use this pattern in production.

Olivier Li

That's certainly an oversight on my part and not something I wanted to do because it happens to pass.

I agree it should be done properly everywhere and I've added explicit scoping.

Line 62, Patchset 17: kInvalidVersion);
Greg Thompson . resolved

nit: instead of testing by changing what is considered the "current" version (which requires adding a data member to each instance), wdyt about modifying a file on-disk to change its version? could either do it via SQL statements or just by flipping bits at offsets 60-63.

Olivier Li

I don't think it's a nit because I was quite annoyed at adding the new member so I'm happy to get rid of it!

I've changed the code to modify the files directly.

Since the test is not very fine grained (Initialize() fails or succeeds) the one thing I don't like about messing with the file is that if I did something very wrong and completely mangled them it would fail just the same as a bad version.

I think that adding a simetrical operation on the success case provides at least some protection.

Open in Gerrit

Related details

Attention is currently required from:
  • Greg Thompson
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: Id160d9b94baff3fd64bc564b12fcd60024542d6a
Gerrit-Change-Number: 7148789
Gerrit-PatchSet: 21
Gerrit-Owner: Olivier Li <oliv...@chromium.org>
Gerrit-Reviewer: Greg Thompson <g...@chromium.org>
Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
Gerrit-Attention: Greg Thompson <g...@chromium.org>
Gerrit-Comment-Date: Thu, 27 Nov 2025 20:04:43 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Greg Thompson <g...@chromium.org>
satisfied_requirement
unsatisfied_requirement
open
diffy

Greg Thompson (Gerrit)

unread,
Nov 28, 2025, 3:56:38 AM11/28/25
to Olivier Li, Chromium LUCI CQ, chromium...@chromium.org
Attention needed from Olivier Li

Greg Thompson added 13 comments

File components/persistent_cache/sqlite/sqlite_backend_impl.h
Line 29, Patchset 23 (Latest): static constexpr int32_t kCurrentUserVersion = 1;
Greg Thompson . unresolved

#include <stdint.h>

File components/persistent_cache/sqlite/sqlite_backend_impl.cc
Line 36, Patchset 23 (Latest):constexpr int32_t kDefaultUserVersion = 0;
Greg Thompson . unresolved

#include <stdint.h>

Line 152, Patchset 23 (Latest): sql::Transaction transaction(&*db_);
Greg Thompson . unresolved

wdyt about avoiding the transaction if in read-only mode? it'll add overhead for no benefit -- we only read the value in that case.

Line 165, Patchset 17: if (!db_->Open(database_path_)) {
Greg Thompson . unresolved

a counter-proposal in pseudocode:

```
read the fist hunk of the db file into a buffer before opening the databse
pull out the ints at offset 68[1] and offset 60[1] in the buffer
if these don't match the current version:
if not vfs_file_set_.read_only():
truncate all files -- the cache becomes a fresh cache file
else:
return error
```

i think this is less code, less overhead, and easier to reason about compared to checking for existence of the table. it also avoids the GPU case of needing to bounce back to the browser process to get a new file when there's a version change (the browser doesn't open the db today). wdyt?

1. [application id](https://sqlite.org/fileformat2.html#application_id)
2. [user version number](https://sqlite.org/fileformat2.html#user_version_number)

(i'm not sure if this is a great idea -- just tossing it out there as a possibility)

Olivier Li

I think that reading the file number directly or truncating adds complications because it probably needs to happen under the lock so the file doesn't change in a non-thread-safe way? It's always going to be safe for exclusive connections but it feels maybe a bit clunky to have some ReadWrite backends feel confident to truncate and not others.

Local debugging shows me that we take the SHARED lock to execute the `get_user_version` statement. So I think that we could issue the truncate if we took the EXCLUSIVE lock first which can be done manually through SanboxedFile.

Alternatively we could also `DROP TABLE` which would implicitly take the lock on setups that require it without changing much in the case of exclusive connections? I quite like the simplicity of truncating the file though.

I've modified the current implementation of the CL to address some of the points raised [simplicity, speed] and once we figure out the lock situation I can have a go at adding the local handling of version mismatch.

Greg Thompson

I think that reading the file number directly or truncating adds complications because it probably needs to happen under the lock so the file doesn't change in a non-thread-safe way? It's always going to be safe for exclusive connections but it feels maybe a bit clunky to have some ReadWrite backends feel confident to truncate and not others.

We need to worry about the shared lock if we expect to ever have multiple connections racing to open the same db for the first time. The API doesn't provide a way to share a connection that hasn't been initialized (`ShareRead{Only,Write}Connection` requires a `PersistentCache`), so the first writer will _always_ be the only one passing through `Initialize()`. In other words: if `Initialize()` is called on a r/w instance and the version doesn't match, we know that this is the first opener so it's safe to clear the db without worrying about extra locking.

Are there any use-cases today where we'd want to fail the first opener without deleting the files?

  • GPU: the process has exclusive r/w access, so clearing the files and starting over is safe and easier than round-tripping back to the browser to get new files. (The browser never even bothers to open the DB.)
  • CodeCache: `PersistentCacheCollection` in the browser always binds before sharing connections, so it is always the only party holding a db. It would be able to delete the files and retry without extra process hopping.

In neither case would we want to retain the out-of-date files. Since we've taken the philosophy of "it's a cache -- delete it at will" elsewhere, why not do so here, too?

Local debugging shows me that we take the SHARED lock to execute the `get_user_version` statement. So I think that we could issue the truncate if we took the EXCLUSIVE lock first which can be done manually through SanboxedFile.

I'm not sure we should manually fiddle with the shared lock. I'm concerned that we could accidentally create some state confusion between SQLite and our lock impl. A `BEGIN EXCLUSIVE` [statement](https://sqlite.org/lang_transaction.html#immediate) will take the write lock from the get-go, so we should do that if the need arises. (I don't think we need to worry about that here, though.)

Alternatively we could also `DROP TABLE` which would implicitly take the lock on setups that require it without changing much in the case of exclusive connections? I quite like the simplicity of truncating the file though.

Good point. Maybe `DROP TABLE` is the safest thing. Otherwise, I think if we were going to go the truncate route, we would want to close the db first and re-open afterwards to be sure that there's no state confusion.

I've modified the current implementation of the CL to address some of the points raised [simplicity, speed] and once we figure out the lock situation I can have a go at adding the local handling of version mismatch.

File components/persistent_cache/sqlite/sqlite_backend_unittest.cc
Line 19, Patchset 23 (Latest):constexpr int64_t kUserVersionOffset = 60;
Greg Thompson . unresolved

iwyu

Line 33, Patchset 23 (Latest): std::optional<BackendStorage> backend_storage_;
Greg Thompson . unresolved

iwyu

Line 44, Patchset 23 (Latest): // bound with different connections with is incorrect.
Greg Thompson . unresolved

nit: "connections, which"

Line 47, Patchset 23 (Latest): ASSERT_TRUE(backend);
Greg Thompson . unresolved

alternatively, remove this and change the line above to:
`ASSERT_NE(SqliteBackendImpl::Bind(std::move(pending_backend)), nullptr);`

you could also get rid of the scoping and just put a comment above it saying something like "// Initialize the database and close it."

Line 51, Patchset 23 (Latest): auto other_pending_backend,
Greg Thompson . unresolved

could reuse `pending_backend` here

Line 54, Patchset 23 (Latest): base::File db_file = other_pending_backend.sqlite_data.db_file.Duplicate();
Greg Thompson . unresolved

wdyt of changing things a little bit so that we `Read` directly from `db_file` here (without duplicating) to verify that the user version is set by the `Bind()` above?

then we can do the second bind to be sure that it succeeds. the final "Other tests rely on..." comment and code can go away.

Line 84, Patchset 23 (Latest): base::File db_file = pending_backend.sqlite_data.db_file.Duplicate();
Greg Thompson . unresolved

instead of duplicating the file for use below, could we move the `Write` after getting the second `PendingBackend` and write directly to the file in it before binding it?

Line 86, Patchset 23 (Latest): auto backend = SqliteBackendImpl::Bind(std::move(pending_backend));
Greg Thompson . unresolved

`ASSERT_NE(..., nullptr);` here, too

Line 111, Patchset 23 (Latest): auto backend = SqliteBackendImpl::Bind(std::move(other_pending_backend));
Greg Thompson . unresolved

`ASSERT_EQ(..., nullptr);`

Open in Gerrit

Related details

Attention is currently required from:
  • Olivier Li
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: Id160d9b94baff3fd64bc564b12fcd60024542d6a
Gerrit-Change-Number: 7148789
Gerrit-PatchSet: 23
Gerrit-Owner: Olivier Li <oliv...@chromium.org>
Gerrit-Reviewer: Greg Thompson <g...@chromium.org>
Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
Gerrit-Attention: Olivier Li <oliv...@chromium.org>
Gerrit-Comment-Date: Fri, 28 Nov 2025 08:56:20 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Greg Thompson <g...@chromium.org>
Comment-In-Reply-To: Olivier Li <oliv...@chromium.org>
satisfied_requirement
unsatisfied_requirement
open
diffy

Olivier Li (Gerrit)

unread,
Dec 1, 2025, 2:20:42 PM12/1/25
to Greg Thompson, Chromium LUCI CQ, chromium...@chromium.org
Attention needed from Greg Thompson

Olivier Li added 14 comments

File components/persistent_cache/sqlite/sqlite_backend_impl.h
Line 29, Patchset 23: static constexpr int32_t kCurrentUserVersion = 1;
Greg Thompson . resolved

#include <stdint.h>

Olivier Li

Done

File components/persistent_cache/sqlite/sqlite_backend_impl.cc
Line 36, Patchset 23:constexpr int32_t kDefaultUserVersion = 0;
Greg Thompson . resolved

#include <stdint.h>

Olivier Li

Done

Line 152, Patchset 23: sql::Transaction transaction(&*db_);
Greg Thompson . resolved

wdyt about avoiding the transaction if in read-only mode? it'll add overhead for no benefit -- we only read the value in that case.

Olivier Li

Sounds good.

Olivier Li

I don't think there are any usecases where you can't get rid of the files no.

Regarding no two opens racing I follow your logic and I think that's right. Maybe I feel like the API would make that a tad more clear if `BackendStorage::MakePendingBackend` somehow returned an object that is not sendable through mojo but I guess we need it for the case of the GPU. In any case I think your explanation makes sense and it's fine to change.

Thank you for bringing up the exclusive transaction that sounds like something that would simplify digging into the file directly if we needed it.

I've had a go at dropping the table when the connection is ReadWrite and testing that the Bind now succeeds. The previous test that validates failure is still needed for read only connections so I've kept and renamed it.

Line 265, Patchset 32 (Latest): if (!stm.is_valid()) {
Olivier Li . unresolved

`DCHECK`s on statement validity removed because they were never correct.

The statement will be invalid on `SQLITE_BUSY` which is not a reason to crash any client.

This became apparent as a problem while trying this CL on CQ because bots started crashing on the line.

File components/persistent_cache/sqlite/sqlite_backend_unittest.cc
Line 19, Patchset 23:constexpr int64_t kUserVersionOffset = 60;
Greg Thompson . resolved

iwyu

Olivier Li

Done

Line 33, Patchset 23: std::optional<BackendStorage> backend_storage_;
Greg Thompson . resolved

iwyu

Olivier Li

Done

Line 44, Patchset 23: // bound with different connections with is incorrect.
Greg Thompson . resolved

nit: "connections, which"

Olivier Li

Done

Line 47, Patchset 23: ASSERT_TRUE(backend);
Greg Thompson . resolved

alternatively, remove this and change the line above to:
`ASSERT_NE(SqliteBackendImpl::Bind(std::move(pending_backend)), nullptr);`

you could also get rid of the scoping and just put a comment above it saying something like "// Initialize the database and close it."

Olivier Li

Sounds good!.

Line 51, Patchset 23: auto other_pending_backend,
Greg Thompson . unresolved

could reuse `pending_backend` here

Olivier Li

How so if I moved it?

Line 54, Patchset 23: base::File db_file = other_pending_backend.sqlite_data.db_file.Duplicate();
Greg Thompson . resolved

wdyt of changing things a little bit so that we `Read` directly from `db_file` here (without duplicating) to verify that the user version is set by the `Bind()` above?

then we can do the second bind to be sure that it succeeds. the final "Other tests rely on..." comment and code can go away.

Olivier Li

SGTM.

Line 84, Patchset 23: base::File db_file = pending_backend.sqlite_data.db_file.Duplicate();
Greg Thompson . resolved

instead of duplicating the file for use below, could we move the `Write` after getting the second `PendingBackend` and write directly to the file in it before binding it?

Olivier Li

No longer possible now that this test validates a read_only connection and we can't write to the file handle of the second pending backend.

I've applied this recommendation to the readwrite test though.

Line 86, Patchset 23: auto backend = SqliteBackendImpl::Bind(std::move(pending_backend));
Greg Thompson . resolved

`ASSERT_NE(..., nullptr);` here, too

Olivier Li

Done

Line 111, Patchset 23: auto backend = SqliteBackendImpl::Bind(std::move(other_pending_backend));
Greg Thompson . resolved

`ASSERT_EQ(..., nullptr);`

Olivier Li

Done

Open in Gerrit

Related details

Attention is currently required from:
  • Greg Thompson
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: Id160d9b94baff3fd64bc564b12fcd60024542d6a
Gerrit-Change-Number: 7148789
Gerrit-PatchSet: 32
Gerrit-Owner: Olivier Li <oliv...@chromium.org>
Gerrit-Reviewer: Greg Thompson <g...@chromium.org>
Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
Gerrit-Attention: Greg Thompson <g...@chromium.org>
Gerrit-Comment-Date: Mon, 01 Dec 2025 19:20:33 +0000
satisfied_requirement
unsatisfied_requirement
open
diffy

Olivier Li (Gerrit)

unread,
Dec 1, 2025, 2:53:23 PM12/1/25
to Greg Thompson, Chromium LUCI CQ, chromium...@chromium.org
Attention needed from Greg Thompson

Olivier Li added 1 comment

File components/persistent_cache/sqlite/sqlite_backend_impl.cc
Line 265, Patchset 32: if (!stm.is_valid()) {
Olivier Li . unresolved

`DCHECK`s on statement validity removed because they were never correct.

The statement will be invalid on `SQLITE_BUSY` which is not a reason to crash any client.

This became apparent as a problem while trying this CL on CQ because bots started crashing on the line.

Olivier Li

For ease of error tracking I think it's better to let the execution of the statement and have access to the errors in the the `sql::Database` metrics.

Open in Gerrit

Related details

Attention is currently required from:
  • Greg Thompson
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: Id160d9b94baff3fd64bc564b12fcd60024542d6a
Gerrit-Change-Number: 7148789
Gerrit-PatchSet: 33
Gerrit-Owner: Olivier Li <oliv...@chromium.org>
Gerrit-Reviewer: Greg Thompson <g...@chromium.org>
Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
Gerrit-Attention: Greg Thompson <g...@chromium.org>
Gerrit-Comment-Date: Mon, 01 Dec 2025 19:53:16 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Olivier Li <oliv...@chromium.org>
satisfied_requirement
unsatisfied_requirement
open
diffy

Olivier Li (Gerrit)

unread,
Dec 1, 2025, 2:58:04 PM12/1/25
to Greg Thompson, Chromium LUCI CQ, chromium...@chromium.org
Attention needed from Greg Thompson

Olivier Li voted and added 2 comments

Votes added by Olivier Li

Commit-Queue+1

2 comments

File components/persistent_cache/sqlite/sqlite_backend_impl.cc
Line 217, Patchset 33 (Parent): DCHECK(stm.is_valid());
Olivier Li . unresolved

DCHECKs on statement validity removed because they were never correct.

The statement will be invalid on SQLITE_BUSY which is not a reason to crash any client.

This became apparent as a problem while trying this CL on CQ because bots started crashing on the line. For ease of error tracking I think it's better to let the execution of the statement fail and have access to the errors in the the sql::Database metrics.

Line 265, Patchset 32: if (!stm.is_valid()) {
Olivier Li . resolved

`DCHECK`s on statement validity removed because they were never correct.

The statement will be invalid on `SQLITE_BUSY` which is not a reason to crash any client.

This became apparent as a problem while trying this CL on CQ because bots started crashing on the line.

Olivier Li

For ease of error tracking I think it's better to let the execution of the statement and have access to the errors in the the `sql::Database` metrics.

Olivier Li

Acknowledged

Open in Gerrit

Related details

Attention is currently required from:
  • Greg Thompson
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: Id160d9b94baff3fd64bc564b12fcd60024542d6a
Gerrit-Change-Number: 7148789
Gerrit-PatchSet: 33
Gerrit-Owner: Olivier Li <oliv...@chromium.org>
Gerrit-Reviewer: Greg Thompson <g...@chromium.org>
Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
Gerrit-Attention: Greg Thompson <g...@chromium.org>
Gerrit-Comment-Date: Mon, 01 Dec 2025 19:57:58 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: Yes
Comment-In-Reply-To: Olivier Li <oliv...@chromium.org>
satisfied_requirement
unsatisfied_requirement
open
diffy

Greg Thompson (Gerrit)

unread,
Dec 2, 2025, 9:14:28 AM12/2/25
to Olivier Li, Chromium LUCI CQ, chromium...@chromium.org
Attention needed from Olivier Li

Greg Thompson added 1 comment

Patchset-level comments
File-level comment, Patchset 34 (Latest):
Greg Thompson . resolved

it's going to take me a day or two to get to this. thanks for your patience.

Open in Gerrit

Related details

Attention is currently required from:
  • Olivier Li
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: Id160d9b94baff3fd64bc564b12fcd60024542d6a
Gerrit-Change-Number: 7148789
Gerrit-PatchSet: 34
Gerrit-Owner: Olivier Li <oliv...@chromium.org>
Gerrit-Reviewer: Greg Thompson <g...@chromium.org>
Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
Gerrit-Attention: Olivier Li <oliv...@chromium.org>
Gerrit-Comment-Date: Tue, 02 Dec 2025 14:14:10 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
satisfied_requirement
unsatisfied_requirement
open
diffy

Greg Thompson (Gerrit)

unread,
Dec 2, 2025, 10:09:28 AM12/2/25
to Olivier Li, Chromium LUCI CQ, chromium...@chromium.org
Attention needed from Olivier Li

Greg Thompson added 6 comments

Patchset-level comments
Greg Thompson . resolved

partial review. sorry for the delay. :-(

File components/persistent_cache/sqlite/sqlite_backend_impl.cc
Line 152, Patchset 34 (Latest): if (!IsReadOnly()) {
transaction.emplace(&*db_);

if (!transaction->Begin()) {
return false;
}
}
Greg Thompson . unresolved
optional suggestion:
```
if (!IsReadOnly() && !transaction.emplace(&*db_).Begin()) {
return false;
}
```
Greg Thompson

I don't think there are any usecases where you can't get rid of the files no.

Regarding no two opens racing I follow your logic and I think that's right. Maybe I feel like the API would make that a tad more clear if `BackendStorage::MakePendingBackend` somehow returned an object that is not sendable through mojo but I guess we need it for the case of the GPU.

We do want callers to send the `PB` from `MakePendingBackend` directly to other processes -- having the browser open the database before sending it to the GPU would add overhead. I don't know why we'd need that.

A caller of `MakePendingBackend` can do one of two things with the instance it receives: 1) send it to another process, or 2) use `PersistentCache::Bind` to open it. It can't do both. If it wishes to have more than one connection to a cache, then it MUST use `PersistentCache::Bind` to open it and then use `BackendStorage::ShareRead{Only,Write}Backend` to get a new `PB` to it. We have a programming error if someone is calling `MakePendingBackend` twice for the same cache.

If you think we need better doc comments around this, please add them.

Line 170, Patchset 34 (Latest): get_user_version_stm.reset();
Greg Thompson . unresolved

nit: drop use of `std::optional<>` around this and use `get_user_version_stm.Reset(/*clear_bound_vars=*/true);` here.

Line 217, Patchset 33 (Parent): DCHECK(stm.is_valid());
Olivier Li . resolved

DCHECKs on statement validity removed because they were never correct.

The statement will be invalid on SQLITE_BUSY which is not a reason to crash any client.

This became apparent as a problem while trying this CL on CQ because bots started crashing on the line. For ease of error tracking I think it's better to let the execution of the statement fail and have access to the errors in the the sql::Database metrics.

Greg Thompson

Acknowledged

File components/persistent_cache/sqlite/sqlite_backend_unittest.cc
Line 51, Patchset 23: auto other_pending_backend,
Greg Thompson . unresolved

could reuse `pending_backend` here

Olivier Li

How so if I moved it?

Greg Thompson

i mean remove `auto` so that the new instance is put into the existing local variable. (apologies for being overly brief!)

Open in Gerrit

Related details

Attention is currently required from:
  • Olivier Li
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: Id160d9b94baff3fd64bc564b12fcd60024542d6a
Gerrit-Change-Number: 7148789
Gerrit-PatchSet: 34
Gerrit-Owner: Olivier Li <oliv...@chromium.org>
Gerrit-Reviewer: Greg Thompson <g...@chromium.org>
Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
Gerrit-Attention: Olivier Li <oliv...@chromium.org>
Gerrit-Comment-Date: Tue, 02 Dec 2025 15:09:05 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
satisfied_requirement
unsatisfied_requirement
open
diffy

Olivier Li (Gerrit)

unread,
Dec 2, 2025, 4:28:24 PM12/2/25
to Greg Thompson, Chromium LUCI CQ, chromium...@chromium.org
Attention needed from Greg Thompson

Olivier Li added 4 comments

File components/persistent_cache/sqlite/sqlite_backend_impl.cc
Line 152, Patchset 34: if (!IsReadOnly()) {

transaction.emplace(&*db_);

if (!transaction->Begin()) {
return false;
}
}
Greg Thompson . resolved
optional suggestion:
```
if (!IsReadOnly() && !transaction.emplace(&*db_).Begin()) {
return false;
}
```
Olivier Li

SGTM!

Line 165, Patchset 17: if (!db_->Open(database_path_)) {
Greg Thompson . resolved
Olivier Li

Agreed.

The case of the GPU makes sense to me. I didn't make that clear in my original comment but I indeed don't think we should rely on having had an Open take place all the time.

Line 170, Patchset 34: get_user_version_stm.reset();
Greg Thompson . resolved

nit: drop use of `std::optional<>` around this and use `get_user_version_stm.Reset(/*clear_bound_vars=*/true);` here.

Olivier Li

Ah right that's nicer thanks.

File components/persistent_cache/sqlite/sqlite_backend_unittest.cc
Line 51, Patchset 23: auto other_pending_backend,
Greg Thompson . resolved

could reuse `pending_backend` here

Olivier Li

How so if I moved it?

Greg Thompson

i mean remove `auto` so that the new instance is put into the existing local variable. (apologies for being overly brief!)

Olivier Li

Ah right! Yeah that sounds good I realize now it's not nice to have the old variable lying around.

Open in Gerrit

Related details

Attention is currently required from:
  • Greg Thompson
Submit Requirements:
    • requirement satisfiedCode-Coverage
    • requirement satisfiedCode-Owners
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedReview-Enforcement
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: chromium/src
    Gerrit-Branch: main
    Gerrit-Change-Id: Id160d9b94baff3fd64bc564b12fcd60024542d6a
    Gerrit-Change-Number: 7148789
    Gerrit-PatchSet: 35
    Gerrit-Owner: Olivier Li <oliv...@chromium.org>
    Gerrit-Reviewer: Greg Thompson <g...@chromium.org>
    Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
    Gerrit-Attention: Greg Thompson <g...@chromium.org>
    Gerrit-Comment-Date: Tue, 02 Dec 2025 21:28:14 +0000
    satisfied_requirement
    unsatisfied_requirement
    open
    diffy

    Greg Thompson (Gerrit)

    unread,
    Dec 3, 2025, 7:58:52 AM12/3/25
    to Olivier Li, Chromium LUCI CQ, chromium...@chromium.org
    Attention needed from Olivier Li

    Greg Thompson added 8 comments

    File components/persistent_cache/sqlite/sqlite_backend_impl.h
    Line 30, Patchset 35 (Latest): // evolves in a non-backwards compatible way.
    Greg Thompson . unresolved

    consider documenting the impact of incrementing: all existing databases of earlier version(s) are cleared on `Bind`.

    File components/persistent_cache/sqlite/sqlite_backend_impl.cc
    Line 143, Patchset 35 (Parent): TRACE_EVENT_INSTANT1("persistent_cache", "open_failed",
    Greg Thompson . unresolved

    just curious: is removing these intentional? should we also remove the ones for find_failed and insert_failed? do they add value over the tracing done in `sql::Database::OnSqliteError`?

    Line 151, Patchset 35 (Latest): std::optional<sql::Transaction> transaction;
    Greg Thompson . unresolved

    could we check the version before starting the transaction so that we only pay that cost when the db needs to be mutuated?

    Line 156, Patchset 35 (Latest): sql::Statement get_user_version_stm(
    db_->GetUniqueStatement("PRAGMA user_version"));
    if (!get_user_version_stm.Step()) {
    return false;
    }

    const int32_t detected_user_version = get_user_version_stm.ColumnInt(0);

    // Reset statement so the database is not locked when we potentially
    // try to drop it.
    get_user_version_stm.Reset(/*clear_bound_vars=*/true);
    Greg Thompson . unresolved
    yet another way to do this so that we don't even need `Reset` and the accompanying comment: :-)
    ```
    int32_t detected_user_version;
    if (sql::Statement get_user_version_stm(
    db_->GetUniqueStatement("PRAGMA user_version"));
    get_user_version_stm.Step()) {
    detected_user_version = get_user_version_stm.ColumnInt(0);
    } else {
    return false;
    }
    ```
    Line 168, Patchset 35 (Latest): if (detected_user_version != kCurrentUserVersion) {
    Greg Thompson . unresolved

    could we return early if they match to reduce indentation?

    Line 180, Patchset 35 (Latest): if (!db_->Execute("CREATE TABLE entries(key TEXT PRIMARY KEY "
    Greg Thompson . resolved

    should we switch to `BLOB` now, or is there a reason to do that separately? hmm. we could switch from `std::string_view` to `base::span<const uint8_t>` for the key at the same time. i've convinced myself that we should do that separately. :-)

    Line 181, Patchset 35 (Latest): "UNIQUE NOT "
    Greg Thompson . unresolved

    nit: could you adjust where the line breaks are so that this is a bit easier to read?

    File components/persistent_cache/sqlite/sqlite_backend_unittest.cc
    Line 117, Patchset 35 (Latest): EXPECT_THAT(cache->Insert(kKey, base::byte_span_from_cstring("1")),
    HasValue());
    Greg Thompson . unresolved

    i recently noticed that we have `EXPECT_OK(...);` for this case.

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Olivier Li
    Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: chromium/src
      Gerrit-Branch: main
      Gerrit-Change-Id: Id160d9b94baff3fd64bc564b12fcd60024542d6a
      Gerrit-Change-Number: 7148789
      Gerrit-PatchSet: 35
      Gerrit-Owner: Olivier Li <oliv...@chromium.org>
      Gerrit-Reviewer: Greg Thompson <g...@chromium.org>
      Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
      Gerrit-Attention: Olivier Li <oliv...@chromium.org>
      Gerrit-Comment-Date: Wed, 03 Dec 2025 12:58:38 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Olivier Li (Gerrit)

      unread,
      Dec 16, 2025, 1:02:26 PM12/16/25
      to Greg Thompson, Chromium LUCI CQ, chromium...@chromium.org
      Attention needed from Greg Thompson

      Olivier Li added 9 comments

      File components/persistent_cache/sqlite/sqlite_backend_impl.h
      Line 30, Patchset 35: // evolves in a non-backwards compatible way.
      Greg Thompson . resolved

      consider documenting the impact of incrementing: all existing databases of earlier version(s) are cleared on `Bind`.

      Olivier Li

      That's a good point. Done.

      File components/persistent_cache/sqlite/sqlite_backend_impl.cc
      Line 143, Patchset 35 (Parent): TRACE_EVENT_INSTANT1("persistent_cache", "open_failed",
      Greg Thompson . resolved

      just curious: is removing these intentional? should we also remove the ones for find_failed and insert_failed? do they add value over the tracing done in `sql::Database::OnSqliteError`?

      Olivier Li

      Removing them was indeed intentional because it didn't make sense to keep them when they were so many new early returns.

      I don't think the others add much and I've gone ahead and removed them.

      Line 151, Patchset 35: std::optional<sql::Transaction> transaction;
      Greg Thompson . resolved

      could we check the version before starting the transaction so that we only pay that cost when the db needs to be mutuated?

      Olivier Li

      Yes we can. I've moved it closer to the mutating calls. It's also no longer an optional because if that point is reached in the code then we know it's needed.

      Line 156, Patchset 35: sql::Statement get_user_version_stm(

      db_->GetUniqueStatement("PRAGMA user_version"));
      if (!get_user_version_stm.Step()) {
      return false;
      }

      const int32_t detected_user_version = get_user_version_stm.ColumnInt(0);

      // Reset statement so the database is not locked when we potentially
      // try to drop it.
      get_user_version_stm.Reset(/*clear_bound_vars=*/true);
      Greg Thompson . resolved
      yet another way to do this so that we don't even need `Reset` and the accompanying comment: :-)
      ```
      int32_t detected_user_version;
      if (sql::Statement get_user_version_stm(
      db_->GetUniqueStatement("PRAGMA user_version"));
      get_user_version_stm.Step()) {
      detected_user_version = get_user_version_stm.ColumnInt(0);
      } else {
      return false;
      }
      ```
      Olivier Li

      Nice!

      Line 168, Patchset 35: if (detected_user_version != kCurrentUserVersion) {
      Greg Thompson . resolved

      could we return early if they match to reduce indentation?

      Olivier Li

      Yep!

      Line 180, Patchset 35: if (!db_->Execute("CREATE TABLE entries(key TEXT PRIMARY KEY "
      Greg Thompson . resolved

      should we switch to `BLOB` now, or is there a reason to do that separately? hmm. we could switch from `std::string_view` to `base::span<const uint8_t>` for the key at the same time. i've convinced myself that we should do that separately. :-)

      Olivier Li

      I've put it up as a dependant CL so we can land the whole chain the save DEV release hopefully and avoid users clearing their caches too many times 😊

      Line 181, Patchset 35: "UNIQUE NOT "
      Greg Thompson . resolved

      nit: could you adjust where the line breaks are so that this is a bit easier to read?

      Olivier Li

      Done

      Line 262, Patchset 40 (Latest): if (!stm.is_valid()) {
      Olivier Li . unresolved

      Note: Validity checks brought back because without them we sometimes trip this: https://source.chromium.org/chromium/chromium/src/+/main:sql/statement.cc;l=98;drc=f8a495827fdd58be86e707d7396a4fad7014a117

      The error code wouldn't have been logged from sql::Database anyways after all because of the early return here: https://source.chromium.org/chromium/chromium/src/+/main:sql/statement.cc;l=118;drc=c8fb498259ce5c219f957dbe367da21a82a361c0

      File components/persistent_cache/sqlite/sqlite_backend_unittest.cc
      Line 117, Patchset 35: EXPECT_THAT(cache->Insert(kKey, base::byte_span_from_cstring("1")),
      HasValue());
      Greg Thompson . resolved

      i recently noticed that we have `EXPECT_OK(...);` for this case.

      Olivier Li

      Neat!

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Greg Thompson
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: chromium/src
      Gerrit-Branch: main
      Gerrit-Change-Id: Id160d9b94baff3fd64bc564b12fcd60024542d6a
      Gerrit-Change-Number: 7148789
      Gerrit-PatchSet: 40
      Gerrit-Owner: Olivier Li <oliv...@chromium.org>
      Gerrit-Reviewer: Greg Thompson <g...@chromium.org>
      Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
      Gerrit-Attention: Greg Thompson <g...@chromium.org>
      Gerrit-Comment-Date: Tue, 16 Dec 2025 18:02:19 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      Comment-In-Reply-To: Greg Thompson <g...@chromium.org>
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Greg Thompson (Gerrit)

      unread,
      Dec 17, 2025, 4:39:37 AM12/17/25
      to Olivier Li, Chromium LUCI CQ, chromium...@chromium.org
      Attention needed from Olivier Li

      Greg Thompson added 10 comments

      File components/persistent_cache/sqlite/sqlite_backend_impl.h
      Line 32, Patchset 40 (Latest): static constexpr int32_t kCurrentUserVersion = 1;
      Greg Thompson . unresolved

      `int` here and everywhere else a version is stored? (see related comment in .cc)

      File components/persistent_cache/sqlite/sqlite_backend_impl.cc
      Line 150, Patchset 40 (Latest): int32_t detected_user_version;
      Greg Thompson . unresolved

      `ColumnInt` returns an `int` -- is there a reason not to use that here?

      Line 150, Patchset 40 (Latest): int32_t detected_user_version;
      Greg Thompson . unresolved
      nit: a comment above this would help make it more clear why we're doing all of this. maybe something like:
      ```
      // Check the user-version (https://sqlite.org/pragma.html#pragma_user_version)
      // to see if there has been a schema change since the last time this database
      // was modified.
      ```
      Line 169, Patchset 40 (Latest): // Begin an explicit transaction so that creating the table and
      Greg Thompson . unresolved
      another comment suggestion above this point:
      ```
      // This is either a new database (user-version has never been set) or was last
      // written with an old schema. Clear it and create the current schema's table.
      ```
      Line 176, Patchset 40 (Latest): // If a table already exists drop it to start over fresh.
      Greg Thompson . unresolved

      this comment isn't needed if you take my suggestion above

      Line 177, Patchset 40 (Latest): if (!db_->Execute("DROP TABLE IF EXISTS entries")) {
      Greg Thompson . unresolved

      maybe it's obvious, but we'll need to adjust this if we ever have a schema with anything other than the one "entries" table. do you think it's worth being explicit about this so that future maintainers don't have to think as much? maybe a comment just above the `CREATE TABLE` statement below saying something like "IMPORTANT: Revise the `DROP TABLE` statement above if more than the one "entries" table is created here."?

      Line 262, Patchset 40 (Latest): if (!stm.is_valid()) {
      Olivier Li . unresolved

      Note: Validity checks brought back because without them we sometimes trip this: https://source.chromium.org/chromium/chromium/src/+/main:sql/statement.cc;l=98;drc=f8a495827fdd58be86e707d7396a4fad7014a117

      The error code wouldn't have been logged from sql::Database anyways after all because of the early return here: https://source.chromium.org/chromium/chromium/src/+/main:sql/statement.cc;l=118;drc=c8fb498259ce5c219f957dbe367da21a82a361c0

      Greg Thompson

      if my read is correct, we should check `stm` immediately after construction. it will be invalid if `sqlite3_prepare_v3` failed, in which case the error will go through `Database::OnSqliteError`. if it's valid at that point, it will stay valid through all `Bind+++` calls even if one of those fails -- a failure to bind doesn't close the statement or the db, does it?

      are you seeing cases where we're failing to get the statement here, or where a statement becomes invalid after creation? if the former, is the failure due to something other than invalid SQL? where are you seeing that we're hitting that `LOG(FATAL) in `CheckValid`?

      File components/persistent_cache/sqlite/sqlite_backend_unittest.cc
      File-level comment, Patchset 40 (Latest):
      Greg Thompson . unresolved

      nit: rename to `components/persistent_cache/sqlite/sqlite_backend_impl_unittest.cc`

      Line 16, Patchset 40 (Latest):#include "components/persistent_cache/sqlite/sqlite_backend_impl.h"
      Greg Thompson . unresolved
      Line 52, Patchset 40 (Latest): const base::FilePath db_path = base::FilePath::FromASCII("Cache");
      Greg Thompson . unresolved

      nit: `db_basename` or somesuch -- this isn't a full path (see BackendStorage::MakePendingBackend)

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Olivier Li
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: chromium/src
      Gerrit-Branch: main
      Gerrit-Change-Id: Id160d9b94baff3fd64bc564b12fcd60024542d6a
      Gerrit-Change-Number: 7148789
      Gerrit-PatchSet: 40
      Gerrit-Owner: Olivier Li <oliv...@chromium.org>
      Gerrit-Reviewer: Greg Thompson <g...@chromium.org>
      Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
      Gerrit-Attention: Olivier Li <oliv...@chromium.org>
      Gerrit-Comment-Date: Wed, 17 Dec 2025 09:39:22 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      Comment-In-Reply-To: Olivier Li <oliv...@chromium.org>
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Olivier Li (Gerrit)

      unread,
      Jan 12, 2026, 10:36:56 AM (19 hours ago) Jan 12
      to AyeAye, Greg Thompson, Chromium LUCI CQ, chromium...@chromium.org, grt+...@chromium.org
      Attention needed from Greg Thompson

      Olivier Li added 9 comments

      File components/persistent_cache/sqlite/sqlite_backend_impl.h
      Line 32, Patchset 40: static constexpr int32_t kCurrentUserVersion = 1;
      Greg Thompson . resolved

      `int` here and everywhere else a version is stored? (see related comment in .cc)

      Olivier Li

      Done

      File components/persistent_cache/sqlite/sqlite_backend_impl.cc
      Line 150, Patchset 40: int32_t detected_user_version;
      Greg Thompson . resolved

      `ColumnInt` returns an `int` -- is there a reason not to use that here?

      Olivier Li

      There isn't really a reason no. Int is fine.

      Line 150, Patchset 40: int32_t detected_user_version;
      Greg Thompson . resolved
      nit: a comment above this would help make it more clear why we're doing all of this. maybe something like:
      ```
      // Check the user-version (https://sqlite.org/pragma.html#pragma_user_version)
      // to see if there has been a schema change since the last time this database
      // was modified.
      ```
      Olivier Li

      I think that makes sense. I used your comment.

      Line 169, Patchset 40: // Begin an explicit transaction so that creating the table and
      Greg Thompson . resolved
      another comment suggestion above this point:
      ```
      // This is either a new database (user-version has never been set) or was last
      // written with an old schema. Clear it and create the current schema's table.
      ```
      Olivier Li

      Good point, I took it just slighty modified to represent the order of the operations.

      Line 176, Patchset 40: // If a table already exists drop it to start over fresh.
      Greg Thompson . resolved

      this comment isn't needed if you take my suggestion above

      Olivier Li

      Done

      Line 177, Patchset 40: if (!db_->Execute("DROP TABLE IF EXISTS entries")) {
      Greg Thompson . resolved

      maybe it's obvious, but we'll need to adjust this if we ever have a schema with anything other than the one "entries" table. do you think it's worth being explicit about this so that future maintainers don't have to think as much? maybe a comment just above the `CREATE TABLE` statement below saying something like "IMPORTANT: Revise the `DROP TABLE` statement above if more than the one "entries" table is created here."?

      Olivier Li

      I think it's obvious while reading the code during active development but a comment doesn't hurt for somebody that would come in for a "quick fix" 😊

      Line 262, Patchset 40: if (!stm.is_valid()) {
      Olivier Li . unresolved

      Note: Validity checks brought back because without them we sometimes trip this: https://source.chromium.org/chromium/chromium/src/+/main:sql/statement.cc;l=98;drc=f8a495827fdd58be86e707d7396a4fad7014a117

      The error code wouldn't have been logged from sql::Database anyways after all because of the early return here: https://source.chromium.org/chromium/chromium/src/+/main:sql/statement.cc;l=118;drc=c8fb498259ce5c219f957dbe367da21a82a361c0

      Greg Thompson

      if my read is correct, we should check `stm` immediately after construction. it will be invalid if `sqlite3_prepare_v3` failed, in which case the error will go through `Database::OnSqliteError`. if it's valid at that point, it will stay valid through all `Bind+++` calls even if one of those fails -- a failure to bind doesn't close the statement or the db, does it?

      are you seeing cases where we're failing to get the statement here, or where a statement becomes invalid after creation? if the former, is the failure due to something other than invalid SQL? where are you seeing that we're hitting that `LOG(FATAL) in `CheckValid`?

      Olivier Li

      I think it makes more sense to return before binding.

      Am I seeing cases where we fail to get a valid statement here: Yes, on SQLITE_BUSY. (So indeed something other than invalid SQL).

      Do I see the statement becoming invalid later: I haven't seen that spcecifically but haven't hunted for it either.

      Where did I hit the fatal LOG? :

      https://ci.chromium.org/ui/p/chromium/builders/try/mac-rel/b8696296373923367985/test-results?q=ExactID%3A%3A%2F%2F%5C%3Aheadless_shell_wpt%21webtest%3A%3Aexternal%2Fwpt%2Ffs%23FileSystemFileHandle-writable-file-stream-back-forward-cache.https.tentative.window.html+VHash%3Accdd26e08b392b3a&clean=

      File components/persistent_cache/sqlite/sqlite_backend_unittest.cc
      Line 16, Patchset 40:#include "components/persistent_cache/sqlite/sqlite_backend_impl.h"
      Greg Thompson . resolved
      Olivier Li

      Done

      Line 52, Patchset 40: const base::FilePath db_path = base::FilePath::FromASCII("Cache");
      Greg Thompson . resolved

      nit: `db_basename` or somesuch -- this isn't a full path (see BackendStorage::MakePendingBackend)

      Olivier Li

      Good point!

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Greg Thompson
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: chromium/src
      Gerrit-Branch: main
      Gerrit-Change-Id: Id160d9b94baff3fd64bc564b12fcd60024542d6a
      Gerrit-Change-Number: 7148789
      Gerrit-PatchSet: 41
      Gerrit-Owner: Olivier Li <oliv...@chromium.org>
      Gerrit-Reviewer: Greg Thompson <g...@chromium.org>
      Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
      Gerrit-Attention: Greg Thompson <g...@chromium.org>
      Gerrit-Comment-Date: Mon, 12 Jan 2026 15:36:49 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Olivier Li (Gerrit)

      unread,
      Jan 12, 2026, 10:58:21 AM (19 hours ago) Jan 12
      to Chromium LUCI CQ, chromium...@chromium.org

      Olivier Li abandoned this change.

      View Change

      Abandoned

      Olivier Li abandoned this change

      Related details

      Attention set is empty
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedReview-Enforcement
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: abandon
      Gerrit-Project: chromium/src
      Gerrit-Branch: main
      Gerrit-Change-Id: Ibe01b0aa5f63a160e39ed82c39e5eaf6dc41585d
      Gerrit-Change-Number: 7132859
      Gerrit-PatchSet: 4
      Gerrit-Owner: Olivier Li <oliv...@chromium.org>
      Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Greg Thompson (Gerrit)

      unread,
      3:49 AM (2 hours ago) 3:49 AM
      to Olivier Li, AyeAye, Chromium LUCI CQ, chromium...@chromium.org, grt+...@chromium.org
      Attention needed from Olivier Li

      Greg Thompson voted and added 2 comments

      Votes added by Greg Thompson

      Code-Review+1

      2 comments

      File components/persistent_cache/sqlite/sqlite_backend_impl.cc
      Line 262, Patchset 40: if (!stm.is_valid()) {
      Olivier Li . resolved

      Note: Validity checks brought back because without them we sometimes trip this: https://source.chromium.org/chromium/chromium/src/+/main:sql/statement.cc;l=98;drc=f8a495827fdd58be86e707d7396a4fad7014a117

      The error code wouldn't have been logged from sql::Database anyways after all because of the early return here: https://source.chromium.org/chromium/chromium/src/+/main:sql/statement.cc;l=118;drc=c8fb498259ce5c219f957dbe367da21a82a361c0

      Greg Thompson

      if my read is correct, we should check `stm` immediately after construction. it will be invalid if `sqlite3_prepare_v3` failed, in which case the error will go through `Database::OnSqliteError`. if it's valid at that point, it will stay valid through all `Bind+++` calls even if one of those fails -- a failure to bind doesn't close the statement or the db, does it?

      are you seeing cases where we're failing to get the statement here, or where a statement becomes invalid after creation? if the former, is the failure due to something other than invalid SQL? where are you seeing that we're hitting that `LOG(FATAL) in `CheckValid`?

      Olivier Li

      I think it makes more sense to return before binding.

      Am I seeing cases where we fail to get a valid statement here: Yes, on SQLITE_BUSY. (So indeed something other than invalid SQL).

      Do I see the statement becoming invalid later: I haven't seen that spcecifically but haven't hunted for it either.

      Where did I hit the fatal LOG? :

      https://ci.chromium.org/ui/p/chromium/builders/try/mac-rel/b8696296373923367985/test-results?q=ExactID%3A%3A%2F%2F%5C%3Aheadless_shell_wpt%21webtest%3A%3Aexternal%2Fwpt%2Ffs%23FileSystemFileHandle-writable-file-stream-back-forward-cache.https.tentative.window.html+VHash%3Accdd26e08b392b3a&clean=

      Greg Thompson

      Acknowledged

      File components/persistent_cache/sqlite/sqlite_backend_unittest.cc
      Line 16, Patchset 40:#include "components/persistent_cache/sqlite/sqlite_backend_impl.h"
      Greg Thompson . unresolved
      Greg Thompson

      did clang-format move it back down here? maybe renaming this test file to sqlite_backend_impl_unittest.cc will allow it to keep the include where it belongs?

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Olivier Li
      Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement satisfiedCode-Owners
      • requirement satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement satisfiedReview-Enforcement
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: chromium/src
      Gerrit-Branch: main
      Gerrit-Change-Id: Id160d9b94baff3fd64bc564b12fcd60024542d6a
      Gerrit-Change-Number: 7148789
      Gerrit-PatchSet: 43
      Gerrit-Owner: Olivier Li <oliv...@chromium.org>
      Gerrit-Reviewer: Greg Thompson <g...@chromium.org>
      Gerrit-Reviewer: Olivier Li <oliv...@chromium.org>
      Gerrit-Attention: Olivier Li <oliv...@chromium.org>
      Gerrit-Comment-Date: Tue, 13 Jan 2026 08:49:34 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: Yes
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy
      Reply all
      Reply to author
      Forward
      0 new messages