You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
New nodes are allocated with NodeStamp(0). When we decide to remove it, as_removed will be called so the stamp of the removed node will be turned to -1. Then if it's reused, the stamp will become 1. If we repeat this, the values of this stamp form a sequence of...
0, -1, 1, -2, 2, ..., 32766, -32767
Here comes the problem: -32767 is still considered reusable since it's greater than i16::MIN = -32768. So if we reuse it, then the stamp will become 32767. However, if we remove it again, we'll get a stamp of -32767 since self.0 < i16::MAX is now false, so we're actually reusing a stamp!
32766, -32767, 32767, -32767, 32767, -32767...
This breaks the assumption that we should use different stamps for different nodes, and the interfaces relying on this may behave incorrectly. For example...
use indextree::Arena;fnmain(){letmut tree:Arena<i32> = Arena::new();for i in0usize.. {let id = tree.new_node(42);assert!(!id.is_removed(&tree));
id.remove(&mut tree);assert!(id.is_removed(&tree));let new_id = tree.new_node(42);assert!(!new_id.is_removed(&tree));a
assert!(id.is_removed(&tree),"i: {i}, id: {id:?}, new_id: {new_id:?}");// <--
new_id.remove(&mut tree);}}
The root cause of this problem is that reusable always return true, which is not expected. So an intuitive solution could be correctly implementing this function.
However, I suppose it's worthwhile revisiting the meaning of stamp's existence. The ultimate purpose of reusing a node is to save memory, so using i16 seems not that convincing here. A node being unresuable means that we have to allocate a new node. For example, if we simply turn the stamp into an i64, we'll have much more chances to reuse a node. The cost of 6 extra bytes between i16 and i64 is much less than allocating a new node, not to mention the memory alignment that may erase the difference.
So I'm wondering whether it's acceptable to claim that indexing with or calling methods on a removed NodeId is always considered to be an undefined behavior, so that we can remove the stamp at all? This could make node reusing always available.
The text was updated successfully, but these errors were encountered:
First of all, I'd like to express my appreciation for your excellent work!
However, I've found a bug that
NodeStamp
does not work as expected when reusing nodes.Problems
indextree/src/id.rs
Lines 40 to 64 in 3eee154
New nodes are allocated with
NodeStamp(0)
. When we decide to remove it,as_removed
will be called so the stamp of the removed node will be turned to-1
. Then if it's reused, the stamp will become1
. If we repeat this, the values of this stamp form a sequence of...Here comes the problem:
-32767
is still consideredreusable
since it's greater thani16::MIN = -32768
. So if we reuse it, then the stamp will become32767
. However, if we remove it again, we'll get a stamp of-32767
sinceself.0 < i16::MAX
is now false, so we're actually reusing a stamp!This breaks the assumption that we should use different stamps for different nodes, and the interfaces relying on this may behave incorrectly. For example...
This code above panics during the 16385th loop.
Possible Solutions
The root cause of this problem is that
reusable
always returntrue
, which is not expected. So an intuitive solution could be correctly implementing this function.However, I suppose it's worthwhile revisiting the meaning of
stamp
's existence. The ultimate purpose of reusing a node is to save memory, so usingi16
seems not that convincing here. A node being unresuable means that we have to allocate a new node. For example, if we simply turn the stamp into ani64
, we'll have much more chances to reuse a node. The cost of 6 extra bytes betweeni16
andi64
is much less than allocating a new node, not to mention the memory alignment that may erase the difference.So I'm wondering whether it's acceptable to claim that indexing with or calling methods on a removed
NodeId
is always considered to be an undefined behavior, so that we can remove thestamp
at all? This could make node reusing always available.The text was updated successfully, but these errors were encountered: