-
Notifications
You must be signed in to change notification settings - Fork 820
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
RavenDB-22261: Inconsistency in Tree external API #18422
base: v6.0
Are you sure you want to change the base?
Conversation
Look at the last commit, this is more of an exploration for standarization of the logic inside Voron. I have a few other places where this approach can simplify a lot the current code. For example, as of right now I identified 10s of places where for example we assume implicitly non-nullness when in fact creation methods can return null among other issues. |
85cf414
to
d214c6f
Compare
if (header == null) | ||
return; | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it's a valid scenario to not read it? Shouldn't we revert the already introduced changes to tree
object? For example we have already changed the name - tree._treeName = ...;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, in fact I thought exactly as you the first time. Maybe I should add more comments. It can happen that we try to repurpose the instance but the instance does not exist / or does not have any item in it. BUT, we should still have repurposed the instance for a new tree. This is one of the big inconsistencies I have to address but I am not addressing it yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any additional thoughts on this after spending more time on it?
if (tree._tx.Flags == TransactionFlags.ReadWrite && (tree._parent.State.Header.Flags & TreeFlags.FixedSizeTrees) != TreeFlags.FixedSizeTrees) | ||
{ | ||
ref var state = ref tree._parent.State.Modify(); | ||
state.Flags |= TreeFlags.FixedSizeTrees; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have serious doubts if this is the place to handle that. It's only about repurposing the instance. IMO it's tricky that we modify the state here. I'd prefer more explicit approach
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, but that is the only way we can be backward compatible with already messed trees.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But in other places we explicitly apply TreeFlags.FixedSizeTrees
flag
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not to apply TreeFlags.FixedSizeTrees
explicitly?
b74b208
to
3a7fdf5
Compare
8f6ada6
to
84f64f8
Compare
84f64f8
to
9f1159e
Compare
if (offset + count > dest.Length) | ||
throw new ArgumentOutOfRangeException(nameof(from), "Cannot copy data after the end of the buffer"); | ||
|
||
ThrowIfOnDebug<InvalidOperationException>(HasValue == false, $"{nameof(HasValue)} is false."); | ||
EnsureIsNotBadPointer(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is EnsureIsNotBadPointer
left as a placeholder for future debugging? Since the method is empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a conditional implementation. This is the VALIDATE
version of it:
[Conditional("VALIDATE")]
internal readonly void EnsureIsNotBadPointer()
{
if (_pointer->Ptr == null)
throw new InvalidOperationException("The inner storage pointer is not initialized. This is a defect on the implementation of the ByteStringContext class");
if (_pointer->Key == ByteStringStorage.NullKey)
throw new InvalidOperationException("The memory referenced has already being released. This is a dangling pointer. Check your .Release() statements and aliases in the calling code.");
if ( this.Key != _pointer->Key)
{
if (this.Key >> 16 != _pointer->Key >> 16)
throw new InvalidOperationException("The owner context for the ByteString and the unmanaged storage are different. Make sure you havent killed the allocator and kept a reference to the ByteString outside of its scope.");
Debug.Assert((this.Key & 0x0000000FFFFFFFF) != (_pointer->Key & 0x0000000FFFFFFFF), "(this.Key & 0x0000000FFFFFFFF) != (_pointer->Key & 0x0000000FFFFFFFF)");
throw new InvalidOperationException("The key for the ByteString and the unmanaged storage are different. This is a dangling pointer. Check your .Release() statements and aliases in the calling code.");
}
}
@@ -1424,14 +1391,16 @@ private SegmentInformation AllocateSegment(int size) | |||
private void ThrowInvalidMemorySegmentOnAllocation() | |||
{ | |||
AllocationFailed?.Invoke(); | |||
throw new InvalidOperationException("Allocate gave us a segment that was already disposed."); | |||
PortableExceptions.Throw<InvalidOperationException>("Allocate gave us a segment that was already disposed."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PortableExceptions.Throw<InvalidOperationException>("Allocate gave us a segment that was already disposed."); | |
Throw<InvalidOperationException>("Allocate gave us a segment that was already disposed."); |
if (_disposed) | ||
ThrowObjectDisposed(); | ||
|
||
PortableExceptions.ThrowIfNull(value._pointer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PortableExceptions.ThrowIfNull(value._pointer); | |
ThrowIfNull(value._pointer); |
ThrowIf<InvalidOperationException>(required > ArenaMemoryAllocator.MaxArenaSize, | ||
$"Tried to allocate {new Size(required, SizeUnit.Bytes)}, which exceeds maximum allocation size of {new Size(ArenaMemoryAllocator.MaxArenaSize, SizeUnit.Bytes)}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we allocate to generate string exception every time even if we do not throw?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point. I will have to look into what the JIT does on that one. Most likely case is that it will get cancelled because the underlying Throw
of the method is marked as [DoesNotReturn]
, but we should look into it anyways.
@@ -36,19 +42,18 @@ public TreeIterator(Tree tree, LowLevelTransaction tx, bool prefetch) | |||
|
|||
public int GetCurrentDataSize() | |||
{ | |||
if (_disposed) | |||
throw new ObjectDisposedException("TreeIterator " + _tree.Name); | |||
ThrowIfDisposedOnDebug(this, "TreeIterator " + _tree.Name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to throw only in debug?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It wasn't the case in the previous code. @redknightlois?
|
||
if (_currentPage == null) | ||
throw new InvalidOperationException("No current page was set"); | ||
ThrowIfDisposedOnDebug(this, "TreeIterator " + _tree.Name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -8,6 +8,7 @@ | |||
using Xunit; | |||
using Voron.Global; | |||
using Xunit.Abstractions; | |||
using Elastic.Clients.Elasticsearch; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unintended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hate resharper and it's automatic additions. Yes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's still here
81b3aa2
to
17b5d13
Compare
Test this in DEBUG |
test this in debug please |
test results in Debug - https://jenkins.hibernatingrhinos.com/job/PR_Tests_Debug/119/ |
9ec7724
to
d45f744
Compare
test this in debug please |
@@ -8,6 +8,7 @@ | |||
using Xunit; | |||
using Voron.Global; | |||
using Xunit.Abstractions; | |||
using Elastic.Clients.Elasticsearch; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's still here
@@ -212,7 +212,9 @@ internal Slice EncodeAndApplyAnalyzer(in FieldMetadata binding, Analyzer analyze | |||
//Function used to generate Slice from query parameters. | |||
//We cannot dispose them before the whole query is executed because they are an integral part of IQueryMatch. | |||
//We know that the Slices are automatically disposed when the transaction is closed so we don't need to track them. | |||
#if !DEBUG |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why only if not debug? I'm worried it might be a potential source of odd issues
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason why you want it in debug is because if you dont do so, you can't debug this method as Visual Studio will trip into not initialized memory and kill the process. (It sucks big time).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ouch :/ Okay I understand, although I'm still a bit concerned about having that only in RElease
[InterpolatedStringHandler] | ||
public readonly ref struct ConditionalThrowInterpolatedStringHandler | ||
{ | ||
private readonly StringBuilder? _builder; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use #nullable enable
in this file? So we won't need to enable it globally but use a file-by-file approach
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, but at least for Voron
, Sparrow
and Corax
expect to see in the next few month added usage of those annotations.
@@ -36,19 +42,18 @@ public TreeIterator(Tree tree, LowLevelTransaction tx, bool prefetch) | |||
|
|||
public int GetCurrentDataSize() | |||
{ | |||
if (_disposed) | |||
throw new ObjectDisposedException("TreeIterator " + _tree.Name); | |||
ThrowIfDisposedOnDebug(this, "TreeIterator " + _tree.Name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It wasn't the case in the previous code. @redknightlois?
|
||
if (_currentPage == null) | ||
throw new InvalidOperationException("No current page was set"); | ||
ThrowIfDisposedOnDebug(this, "TreeIterator " + _tree.Name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (header == null) | ||
return; | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any additional thoughts on this after spending more time on it?
if (tree._tx.Flags == TransactionFlags.ReadWrite && (tree._parent.State.Header.Flags & TreeFlags.FixedSizeTrees) != TreeFlags.FixedSizeTrees) | ||
{ | ||
ref var state = ref tree._parent.State.Modify(); | ||
state.Flags |= TreeFlags.FixedSizeTrees; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not to apply TreeFlags.FixedSizeTrees
explicitly?
I don't understand why it does not allow me to comment on your comments. So I will go one by one with quotes, but first thing first. I hate resharper and it adding back stuff I explicitly remove.
Because one of the important changes is to make the ability to modify the state explicit. In fact, this uncovered a bug that had no impact but it was already there. It was an inconsistency on what we stored at the level of the parent.
Yes, we are handling it explicitly on the caller now.
I inspected every single call manually, there is no case where it makes sense to do that check on runtime. I still leave the check on Debug for 2 reasons.
|
d45f744
to
f7351cb
Compare
test this in debug please |
@@ -53,7 +51,7 @@ public void CanRenameTree() | |||
} | |||
} | |||
|
|||
[Fact] | |||
[RavenFact(RavenTestCategory.Voron)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you switched to RavenFact
please adjust - AllTestsShouldUseRavenFactOrRavenTheoryAttributes test
@redknightlois what is the status here?
? |
We need to do a performance run to avoid regressions, correct. |
Applied readonly modifiers where applicable Fixed mispellings Code that is no longer used because of replacements.
…ng interpolation method
f7351cb
to
43846a4
Compare
Issue link
https://issues.hibernatingrhinos.com/issue/RavenDB-22261
Additional description
This is a series of commits aimed at homogenizing the external API of Voron data structures in order to simplify the code and avoid edge cases that arise from inconsistent usage patterns along the codebase.
Type of change
How risky is the change?
Backward compatibility
Is it platform specific issue?
Documentation update
Documentation Required
tag.Testing by Contributor
private
)Testing by RavenDB QA team
QA Required
tag.Is there any existing behavior change of other features due to this change?
UI work
Studio Required
tag.