Merge branch 'cassandra-5.0' into cassandra-6.0

This commit is contained in:
Benedict Elliott Smith 2026-06-02 13:48:31 +01:00
commit e278f0cbbc
3 changed files with 81 additions and 73 deletions

View File

@ -42,6 +42,7 @@ Merged from 4.1:
* Add Paxos v2 option and informatin in cassandra.yaml (CASSANDRA-21316)
* Harden data resurrection startup check with atomic heartbeat file write with fallback (CASSANDRA-21290)
Merged from 4.0:
* BTree.FastBuilder.reset() fails to clear savedBuffer and savedNextKey, causing ClassCastException and SSTable header corruption during schema disagreement (CASSANDRA-21216, CASSANDRA-21260)
* Backport CASSANDRA-17810 fix and improve RTBoundValidator error messages (CASSANDRA-18282)
* Rate limit password changes (CASSANDRA-21202)

View File

@ -3286,7 +3286,7 @@ public class BTree
* was constructed from for the contents of {@code buffer}.
* <p>
* For {@link FastBuilder} these are mostly the same, so they are fetched from a global cache and
* resized accordingly, but for {@link AbstractUpdater} we maintain a buffer of sizes.
* resized accordingly, but for {@link Updater} we maintain a buffer of sizes.
*/
int setDrainSizeMap(Object[] original, int keysInOriginal, Object[] branch, int keysInBranch)
{
@ -3315,7 +3315,7 @@ public class BTree
* was constructed from for the contents of {@code savedBuffer}.
* <p>
* For {@link FastBuilder} these are always the same size, so they are fetched from a global cache,
* but for {@link AbstractUpdater} we maintain a buffer of sizes.
* but for {@link Updater} we maintain a buffer of sizes.
*
* @return the size of {@code branch}
*/
@ -3346,7 +3346,7 @@ public class BTree
* was constructed from the contents of both {@code savedBuffer} and {@code buffer}
* <p>
* For {@link FastBuilder} these are mostly the same size, so they are fetched from a global cache
* and only the last items updated, but for {@link AbstractUpdater} we maintain a buffer of sizes.
* and only the last items updated, but for {@link Updater} we maintain a buffer of sizes.
*/
void setRedistributedSizeMap(Object[] branch, int steal)
{
@ -3485,11 +3485,60 @@ public class BTree
/**
* Clear any references we might still retain, to avoid holding onto memory.
* <p>
* While this method is not strictly necessary, it exists to
* ensure the implementing classes are aware they must handle it.
*/
abstract void reset();
void reset()
{
leaf().count = 0;
clearLeafBuffer(leaf().buffer);
if (leaf().savedBuffer != null)
clearLeafBuffer(leaf().savedBuffer);
leaf().savedNextKey = null;
BranchBuilder branch = leaf().parent;
while (branch != null && branch.inUse)
{
branch.count = 0;
branch.hasRightChild = false;
clearBranchBuffer(branch.buffer);
if (branch.savedBuffer != null)
clearBranchBuffer(branch.savedBuffer);
branch.savedNextKey = null;
branch.clearSourceNode();
branch.inUse = false;
branch = branch.parent;
}
Invariants.require(branch == null || (branch.count == 0 && !branch.hasRightChild));
}
/**
* Clear the contents of a leaf buffer, aborting once we encounter a null entry
* to save time on small trees
*/
private void clearLeafBuffer(Object[] array)
{
if (array[0] == null)
return;
// find first null entry; loop from beginning, to amortise cost over size of working set
int i = 1;
while (i < array.length && array[i] != null)
++i;
Arrays.fill(array, 0, i, null);
}
/**
* Clear the contents of a branch buffer, aborting once we encounter a null entry
* to save time on small trees
*/
private void clearBranchBuffer(Object[] array)
{
if (array[0] == null && array[MAX_KEYS] == null)
return;
// find first null entry; loop from beginning, to amortise cost over size of working set
int i = 1;
while (i < MAX_KEYS && array[i] != null)
++i;
Arrays.fill(array, 0, i, null);
Arrays.fill(array, MAX_KEYS, MAX_KEYS + i + 1, null);
}
}
/**
@ -3544,14 +3593,7 @@ public class BTree
@Override
public void reset()
{
Arrays.fill(leaf().buffer, null);
leaf().count = 0;
BranchBuilder branch = leaf().parent;
while (branch != null && branch.inUse)
{
branch.reset();
branch = branch.parent;
}
super.reset();
}
public boolean validateEmpty()
@ -3580,62 +3622,6 @@ public class BTree
}
}
private static abstract class AbstractUpdater extends AbstractFastBuilder implements AutoCloseable
{
void reset()
{
assert leaf().count == 0;
clearLeafBuffer(leaf().buffer);
if (leaf().savedBuffer != null)
Arrays.fill(leaf().savedBuffer, null);
BranchBuilder branch = leaf().parent;
while (branch != null && branch.inUse)
{
branch.count = 0;
branch.hasRightChild = false;
clearBranchBuffer(branch.buffer);
if (branch.savedBuffer != null && branch.savedBuffer[0] != null)
Arrays.fill(branch.savedBuffer, null); // by definition full, if non-empty
branch.inUse = false;
branch = branch.parent;
}
Invariants.require(branch == null || (branch.count == 0 && !branch.hasRightChild));
}
/**
* Clear the contents of a branch buffer, aborting once we encounter a null entry
* to save time on small trees
*/
private void clearLeafBuffer(Object[] array)
{
if (array[0] == null)
return;
// find first null entry; loop from beginning, to amortise cost over size of working set
int i = 1;
while (i < array.length && array[i] != null)
++i;
Arrays.fill(array, 0, i, null);
}
/**
* Clear the contents of a branch buffer, aborting once we encounter a null entry
* to save time on small trees
*/
private void clearBranchBuffer(Object[] array)
{
if (array[0] == null)
return;
// find first null entry; loop from beginning, to amortise cost over size of working set
int i = 1;
while (i < MAX_KEYS && array[i] != null)
++i;
Arrays.fill(array, 0, i, null);
Arrays.fill(array, MAX_KEYS, MAX_KEYS + i + 1, null);
}
}
/**
* A pooled object for modifying an existing tree with a new (typically smaller) tree.
* <p>
@ -3650,7 +3636,7 @@ public class BTree
* Searches within both trees to accelerate the process of modification, instead of performing a simple
* iteration over the new tree.
*/
static class Updater<Compare, Existing extends Compare, Insert extends Compare> extends AbstractUpdater implements AutoCloseable
static class Updater<Compare, Existing extends Compare, Insert extends Compare> extends AbstractFastBuilder implements AutoCloseable
{
static final TinyThreadLocalPool<Updater> POOL = new TinyThreadLocalPool<>();
TinyThreadLocalPool.TinyPool pool;
@ -3869,7 +3855,7 @@ public class BTree
* <p>
* The approach taken here hopefully balances simplicity, garbage generation and execution time.
*/
static abstract class AbstractSeekingTransformer<I, O> extends AbstractUpdater implements AutoCloseable
static abstract class AbstractSeekingTransformer<I, O> extends AbstractFastBuilder implements AutoCloseable
{
/**
* An iterator over the tree we are updating

View File

@ -428,6 +428,27 @@ public class BTreeTest
assertEquals(count, i);
}
@Test
public void testFastBuilderResetClearsSavedState()
{
// Add >31 items to trigger overflow (savedBuffer/savedNextKey population)
try (BTree.FastBuilder<Integer> builder = BTree.fastBuilder())
{
for (int i = 0; i < 40; i++)
builder.add(i);
// Simulate an abandoned builder (e.g. exception during deserialization)
// by closing without calling build(). close() calls reset() then returns
// the builder to the pool.
}
// Reuse the pooled builder it should be clean
try (BTree.FastBuilder<Integer> builder = BTree.fastBuilder())
{
assertTrue("FastBuilder should be empty after reset, but savedBuffer/savedNextKey leaked",
builder.validateEmpty());
}
}
/**
* <code>UpdateFunction</code> that count the number of call made to apply for each value.
*/