Skip to content

Conversation

MathieuDutSik
Copy link
Contributor

Motivation

The reading of multiple keys in CollectionView does not work. This is unfortunate since the underlying
container of CollectionView is async_lock::RwLock which allows multiple read or one write. We CollectionView
should have the same semantics.

Fixes #4588

Proposal

The following steps are made:

  • The ReadGuardedView is now an enum. The Loaded entry is when it is present in updates. The NotLoaded when accessed from storage.
  • The NotLoaded contains some lock so as to avoid a write to be created and lead to incoherent state.
  • As a consequence, when doing the try_load_entry of a view on storage, the updates is not changed. But that is fine, since it is anyway in the LRU cache.
  • A try_load_entries has been introduced since we can now read multiple keys at once.
  • The code of fn entries has been simplifed to use those functions.
  • The try_load_entries no longer take values since only references are needed.
  • The Clone requirement have been alleviated in CollectionView / ReentrantCollectionView.

Test Plan

The CI.
The unit tests in the code should be sufficient for the functionality.

Release Plan

It addresses an issue that showed up in the GameOfLife. So, moving it to TestNet seems like.a good idea.

Links

None.

@MathieuDutSik MathieuDutSik marked this pull request as ready for review September 19, 2025 09:40
Copy link
Contributor

@ma2bd ma2bd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great overall. try_load_entries could use another pair of eyes

let mut updates = self
.updates
.try_write()
.ok_or(ViewError::CannotAcquireCollectionEntry)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's great to finally remove this! We can remove the comment "May fail if one subview is already being visited."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make sure to do another (non-backportable) PR after this one to remove CannotAcquireCollectionEntry.

}

Ok(values)
let values = self.try_load_entries(&keys).await?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

match updates.get(short_key) {
Some(update) => match update {
Update::Removed => Ok(None),
_ => Ok(Some(ReadGuardedView::Loaded {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using _ in match is an anti-pattern: when you add new variants to an enum compiler won't warn you about mishandling.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more occurences of the same pattern – fix all of them.

Copy link
Contributor

@ma2bd ma2bd Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would generally agree. Here the enum is defined in the same file and unlikely to be changed without checking all the occurrences, but at the same time it is also very easy to replace the few entries.

.into_iter()
.zip(keys)
.map(|(value, key)| match value {
None => Err(missing_key_error(&key)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be OK if one of the requested entries is not found, while others are. We shouldn't fail the whole query.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imagine if we query for an ERC20-like app with 100 addreses – 99 are present but 1 is missing. Does the whole query fail? It'd be bad.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a change of API.
It should be done in another PR, I think.
Also, right now, we do not have support for GraphQL for Solidity contracts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not about Solidity, Game of Life does the same thing. Portal could query all users' scores (or batch it) instead of doing 12k separate queries – each of a new user.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and?
The Game Of Life is returning an error if 1 of the keys being queried is missing. Changing that would be a change of API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a breaking change:

  • Currently, if one key is missing in the ReentrantCollectionView, then the whole query fails.
  • After this PR we have the same behavior.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The label filters inside MapInput suggests that we return the existing entries at the intersection of all filters. The list of keys being a "filter", it is a bug to return an error if some keys don't exist.

Copy link
Contributor

@ma2bd ma2bd Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if one key is missing in the ReentrantCollectionView, then the whole query fails.

Yes but that's a bug (when we use MapInput)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The structure of the queries is somewhat odd:

  • The input to the fn entries is an Option<MapInput>.
  • The MapInput encapsulates an Option<MapFilters>.
  • The MapFilters encapsulates a Vec<K>.

So, you have 3 levels of Option but that does not matter as it gets collapsed for MapView, ReentrantCollectionView, CollectionView as one single Option. All 3 handle the entries in essentially the same way.

It is a little unclear to me why this special structure is needed. There are few calls to the entries function in the code, the examples and related code using Linera. And those calls ask for the whole of entries. In fact all the calls to entries ask for all the keys (so None, wherever that is put in the hierarchy).

There is no notion of filter in GraphQL as a search on the reference https://spec.graphql.org/October2021/ does not show up any matching for filter. So, whatever meaning there is to filter is one that we decide by ourselves.

The SimpleObject macro when applied to a struct, will provide implementations to a BTreeMap, which in case of a missing key will return null. But of course, we are not forced to follow this convention, and if we decide that a filters changes thing, then why not.

Copy link
Contributor

@ma2bd ma2bd Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this internally on slack just one or two days ago.

@MathieuDutSik MathieuDutSik added this pull request to the merge queue Sep 24, 2025
Merged via the queue into linera-io:main with commit c31634e Sep 24, 2025
51 of 52 checks passed
@MathieuDutSik MathieuDutSik deleted the multi_read_collection_view branch September 24, 2025 04:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

linera-views: nested CollectionView GraphQL queries are not supported
3 participants