Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Depreciated legacy tree map #963

Merged
merged 6 commits into from
Nov 29, 2022

Conversation

DavidM-D
Copy link
Contributor

@DavidM-D DavidM-D commented Nov 15, 2022

I have the alternative of marking each individual use of legacy tree map in legacy_tree_map.rs as depreciated rather than the whole module, but since the whole source file is depreciated I figured it wasn't worth the effort.

Resolves #868

@DavidM-D DavidM-D changed the title Depreciated legacy tree map #868 Depreciated legacy tree map Nov 15, 2022
@@ -18,6 +20,7 @@ use crate::IntoStorageKey;
/// - `above`/`below`: O(log(N))
/// - `range` of K elements: O(Klog(N))
///
#[deprecated(since = "4.1.0", note = "Use tree_map::TreeMap instead")]
Copy link
Contributor

Choose a reason for hiding this comment

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

Unfortunately, it's not that simple. Just swapping to use this type is a state-breaking change. At least the note should indicate this.

Also, the tree_map module is not exposed, so you might want to change this to say near_sdk::collections::TreeMap instead.

Optionally, this PR could introduce some migration of the old collection to the new one as a method, for example something like LegacyTreeMap::to_tree_map(&self) or something like this and then this comment can point to this to make the migration easy. This is less important, though, as I presume no one is really using this and if they absolutely want it, they can just ignore the deprecation warnings

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So many mistakes in one line!

@DavidM-D
Copy link
Contributor Author

An issue I'm having with this migration code is that it appears to be hitting the gas limit after successive runs. How should I go about resetting the gas limit after each run?

The gas limit also seems very low, it seems to fall over with 100 elements, is that to be expected or am I doing something stupid?

Comment on lines 590 to 593
// We read this into the heap first so one can use the same prefix without getting into trouble
let elements: Vec<(K, V)> = self.into_iter().collect();
let mut tm = super::TreeMap::new(prefix);
for (k,v) in elements.iter() {
Copy link
Contributor

Choose a reason for hiding this comment

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

This all happens synchronously, what would be a case that this would solve a race condition that using the iterator directly in the loop wouldn't?

@@ -585,6 +585,16 @@ where
self.save(&n);
self.tree.pop();
}

pub fn to_tree_map<S: IntoStorageKey>(self, prefix: S) -> super::TreeMap<K, V> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
pub fn to_tree_map<S: IntoStorageKey>(self, prefix: S) -> super::TreeMap<K, V> {
pub fn into_tree_map<S: IntoStorageKey>(self, prefix: S) -> super::TreeMap<K, V> {

Just to follow https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv naming conventions.

Also, would be nice to have a small comment describing this fn


pub fn to_tree_map<S: IntoStorageKey>(self, prefix: S) -> super::TreeMap<K, V> {
// We read this into the heap first so one can use the same prefix without getting into trouble
let elements: Vec<(K, V)> = self.into_iter().collect();
Copy link
Contributor

Choose a reason for hiding this comment

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

So I'm curious about if this should be clearing the state when doing this conversion.

Seems like if the API is (self) -> Self it would be intuitive that the storage would be cleared and the new collection would be filled with this data.

Otherwise, we could consider this pattern of not clearing and have the method be something like:

fn copy_to_tree_map(&self, ..) -> super::TreeMap...

with a better name and whatnot.

Also, the other part that might seem opinionated is if the prefix is added as a parameter here. This makes sense if we go the copy route, but if the API is for an owned self, it might be more intuitive to just remove the prefix and re-use the one from which you're clearing.

To summarize, my intuitions are that this should either:

  • Remove the prefix from parameters, re-use and clear the state, put state back in at the same prefix
  • Change this API to &self and make it clear that the elements are not being removed from storage
    • Clearly document this fact and make it clear to devs that they should not use the same prefix as before (seems like a footgun and also requires devs to manually clear the state of the old collection)
  • Another option for fun: Just create a drain iterator that clears and gives all key-value pairs back. Let the user decide if they want to clear the elements and put it into another collection or to just use the current iterator and use that to just fill another collection
    • This gives more freedom to migrate this state into other data structures, rather than just the collections::ltm -> collections::tm path

Seems like it might be good to get another opinion on this. @itegulov, you might be most familiar with these APIs, wdyt?

@austinabell
Copy link
Contributor

An issue I'm having with this migration code is that it appears to be hitting the gas limit after successive runs. How should I go about resetting the gas limit after each run?

The gas limit also seems very low, it seems to fall over with 100 elements, is that to be expected or am I doing something stupid?

That sounds expected. This tree-map is very inefficient. I wonder if that's even more reason to do a drain(..) iterator for this so that the migration could be done in chunks.

@DavidM-D
Copy link
Contributor Author

OK I'll remove the migration code until someone asks for it, then we can better understand their use case

@@ -18,6 +20,10 @@ use crate::IntoStorageKey;
/// - `above`/`below`: O(log(N))
/// - `range` of K elements: O(Klog(N))
///
#[deprecated(
since = "4.1.0",
note = "Use near_sdk::collections::TreeMap, if you have an existing LegacyTreeMap use LegacyTreeMap::to_tree_map to migrate"
Copy link
Contributor

Choose a reason for hiding this comment

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

So this comment is out of date now, just remove the comment about migration and this is good

@@ -18,6 +20,7 @@ use crate::IntoStorageKey;
/// - `above`/`below`: O(log(N))
/// - `range` of K elements: O(Klog(N))
///
#[deprecated(since = "4.1.0", note = "Use near_sdk::collections::TreeMap")]
Copy link
Contributor

Choose a reason for hiding this comment

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

noting that the since "4.1.0" is a bit unideal, but given we don't really have a plan what will be the next minor/major version, and no one uses this, it's probably fine

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So yeah, how do we feel about depreciations? Are the major release only, or do we just do it whenever?

@DavidM-D DavidM-D merged commit 1849287 into near:master Nov 29, 2022
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.

Deprecate LegacyTreeMap
2 participants