Skip to content

Commit e278f0c

Browse files
committed
Merge branch 'cassandra-5.0' into cassandra-6.0
2 parents c0999f0 + 1656a9c commit e278f0c

3 files changed

Lines changed: 81 additions & 73 deletions

File tree

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Merged from 4.1:
4242
* Add Paxos v2 option and informatin in cassandra.yaml (CASSANDRA-21316)
4343
* Harden data resurrection startup check with atomic heartbeat file write with fallback (CASSANDRA-21290)
4444
Merged from 4.0:
45+
* BTree.FastBuilder.reset() fails to clear savedBuffer and savedNextKey, causing ClassCastException and SSTable header corruption during schema disagreement (CASSANDRA-21216, CASSANDRA-21260)
4546
* Backport CASSANDRA-17810 fix and improve RTBoundValidator error messages (CASSANDRA-18282)
4647
* Rate limit password changes (CASSANDRA-21202)
4748

src/java/org/apache/cassandra/utils/btree/BTree.java

Lines changed: 59 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -3286,7 +3286,7 @@ && areIdentical(buffer, MAX_KEYS, sourceNode, count, count + 1))
32863286
* was constructed from for the contents of {@code buffer}.
32873287
* <p>
32883288
* For {@link FastBuilder} these are mostly the same, so they are fetched from a global cache and
3289-
* resized accordingly, but for {@link AbstractUpdater} we maintain a buffer of sizes.
3289+
* resized accordingly, but for {@link Updater} we maintain a buffer of sizes.
32903290
*/
32913291
int setDrainSizeMap(Object[] original, int keysInOriginal, Object[] branch, int keysInBranch)
32923292
{
@@ -3315,7 +3315,7 @@ int setDrainSizeMap(Object[] original, int keysInOriginal, Object[] branch, int
33153315
* was constructed from for the contents of {@code savedBuffer}.
33163316
* <p>
33173317
* For {@link FastBuilder} these are always the same size, so they are fetched from a global cache,
3318-
* but for {@link AbstractUpdater} we maintain a buffer of sizes.
3318+
* but for {@link Updater} we maintain a buffer of sizes.
33193319
*
33203320
* @return the size of {@code branch}
33213321
*/
@@ -3346,7 +3346,7 @@ int setOverflowSizeMap(Object[] branch, int keys)
33463346
* was constructed from the contents of both {@code savedBuffer} and {@code buffer}
33473347
* <p>
33483348
* For {@link FastBuilder} these are mostly the same size, so they are fetched from a global cache
3349-
* and only the last items updated, but for {@link AbstractUpdater} we maintain a buffer of sizes.
3349+
* and only the last items updated, but for {@link Updater} we maintain a buffer of sizes.
33503350
*/
33513351
void setRedistributedSizeMap(Object[] branch, int steal)
33523352
{
@@ -3485,11 +3485,60 @@ final LeafBuilder leaf()
34853485

34863486
/**
34873487
* Clear any references we might still retain, to avoid holding onto memory.
3488-
* <p>
3489-
* While this method is not strictly necessary, it exists to
3490-
* ensure the implementing classes are aware they must handle it.
34913488
*/
3492-
abstract void reset();
3489+
void reset()
3490+
{
3491+
leaf().count = 0;
3492+
clearLeafBuffer(leaf().buffer);
3493+
if (leaf().savedBuffer != null)
3494+
clearLeafBuffer(leaf().savedBuffer);
3495+
leaf().savedNextKey = null;
3496+
BranchBuilder branch = leaf().parent;
3497+
while (branch != null && branch.inUse)
3498+
{
3499+
branch.count = 0;
3500+
branch.hasRightChild = false;
3501+
clearBranchBuffer(branch.buffer);
3502+
if (branch.savedBuffer != null)
3503+
clearBranchBuffer(branch.savedBuffer);
3504+
branch.savedNextKey = null;
3505+
branch.clearSourceNode();
3506+
branch.inUse = false;
3507+
branch = branch.parent;
3508+
}
3509+
Invariants.require(branch == null || (branch.count == 0 && !branch.hasRightChild));
3510+
}
3511+
3512+
/**
3513+
* Clear the contents of a leaf buffer, aborting once we encounter a null entry
3514+
* to save time on small trees
3515+
*/
3516+
private void clearLeafBuffer(Object[] array)
3517+
{
3518+
if (array[0] == null)
3519+
return;
3520+
// find first null entry; loop from beginning, to amortise cost over size of working set
3521+
int i = 1;
3522+
while (i < array.length && array[i] != null)
3523+
++i;
3524+
Arrays.fill(array, 0, i, null);
3525+
}
3526+
3527+
/**
3528+
* Clear the contents of a branch buffer, aborting once we encounter a null entry
3529+
* to save time on small trees
3530+
*/
3531+
private void clearBranchBuffer(Object[] array)
3532+
{
3533+
if (array[0] == null && array[MAX_KEYS] == null)
3534+
return;
3535+
// find first null entry; loop from beginning, to amortise cost over size of working set
3536+
int i = 1;
3537+
while (i < MAX_KEYS && array[i] != null)
3538+
++i;
3539+
Arrays.fill(array, 0, i, null);
3540+
Arrays.fill(array, MAX_KEYS, MAX_KEYS + i + 1, null);
3541+
}
34933542
}
34943543

34953544
/**
@@ -3544,14 +3593,7 @@ public void close()
35443593
@Override
35453594
public void reset()
35463595
{
3547-
Arrays.fill(leaf().buffer, null);
3548-
leaf().count = 0;
3549-
BranchBuilder branch = leaf().parent;
3550-
while (branch != null && branch.inUse)
3551-
{
3552-
branch.reset();
3553-
branch = branch.parent;
3554-
}
3596+
super.reset();
35553597
}
35563598

35573599
public boolean validateEmpty()
@@ -3580,62 +3622,6 @@ private static boolean hasOnlyNulls(Object[] buffer)
35803622
}
35813623
}
35823624

3583-
private static abstract class AbstractUpdater extends AbstractFastBuilder implements AutoCloseable
3584-
{
3585-
void reset()
3586-
{
3587-
assert leaf().count == 0;
3588-
clearLeafBuffer(leaf().buffer);
3589-
if (leaf().savedBuffer != null)
3590-
Arrays.fill(leaf().savedBuffer, null);
3591-
3592-
BranchBuilder branch = leaf().parent;
3593-
while (branch != null && branch.inUse)
3594-
{
3595-
branch.count = 0;
3596-
branch.hasRightChild = false;
3597-
clearBranchBuffer(branch.buffer);
3598-
if (branch.savedBuffer != null && branch.savedBuffer[0] != null)
3599-
Arrays.fill(branch.savedBuffer, null); // by definition full, if non-empty
3600-
branch.inUse = false;
3601-
branch = branch.parent;
3602-
}
3603-
Invariants.require(branch == null || (branch.count == 0 && !branch.hasRightChild));
3604-
}
3605-
3606-
/**
3607-
* Clear the contents of a branch buffer, aborting once we encounter a null entry
3608-
* to save time on small trees
3609-
*/
3610-
private void clearLeafBuffer(Object[] array)
3611-
{
3612-
if (array[0] == null)
3613-
return;
3614-
// find first null entry; loop from beginning, to amortise cost over size of working set
3615-
int i = 1;
3616-
while (i < array.length && array[i] != null)
3617-
++i;
3618-
Arrays.fill(array, 0, i, null);
3619-
}
3620-
3621-
/**
3622-
* Clear the contents of a branch buffer, aborting once we encounter a null entry
3623-
* to save time on small trees
3624-
*/
3625-
private void clearBranchBuffer(Object[] array)
3626-
{
3627-
if (array[0] == null)
3628-
return;
3629-
3630-
// find first null entry; loop from beginning, to amortise cost over size of working set
3631-
int i = 1;
3632-
while (i < MAX_KEYS && array[i] != null)
3633-
++i;
3634-
Arrays.fill(array, 0, i, null);
3635-
Arrays.fill(array, MAX_KEYS, MAX_KEYS + i + 1, null);
3636-
}
3637-
}
3638-
36393625
/**
36403626
* A pooled object for modifying an existing tree with a new (typically smaller) tree.
36413627
* <p>
@@ -3650,7 +3636,7 @@ private void clearBranchBuffer(Object[] array)
36503636
* Searches within both trees to accelerate the process of modification, instead of performing a simple
36513637
* iteration over the new tree.
36523638
*/
3653-
static class Updater<Compare, Existing extends Compare, Insert extends Compare> extends AbstractUpdater implements AutoCloseable
3639+
static class Updater<Compare, Existing extends Compare, Insert extends Compare> extends AbstractFastBuilder implements AutoCloseable
36543640
{
36553641
static final TinyThreadLocalPool<Updater> POOL = new TinyThreadLocalPool<>();
36563642
TinyThreadLocalPool.TinyPool pool;
@@ -3869,7 +3855,7 @@ static int searchResultToComparison(int searchResult)
38693855
* <p>
38703856
* The approach taken here hopefully balances simplicity, garbage generation and execution time.
38713857
*/
3872-
static abstract class AbstractSeekingTransformer<I, O> extends AbstractUpdater implements AutoCloseable
3858+
static abstract class AbstractSeekingTransformer<I, O> extends AbstractFastBuilder implements AutoCloseable
38733859
{
38743860
/**
38753861
* An iterator over the tree we are updating

test/unit/org/apache/cassandra/utils/btree/BTreeTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,27 @@ private static void checkResult(int count, Object[] btree, BTree.Dir dir)
428428
assertEquals(count, i);
429429
}
430430

431+
@Test
432+
public void testFastBuilderResetClearsSavedState()
433+
{
434+
// Add >31 items to trigger overflow (savedBuffer/savedNextKey population)
435+
try (BTree.FastBuilder<Integer> builder = BTree.fastBuilder())
436+
{
437+
for (int i = 0; i < 40; i++)
438+
builder.add(i);
439+
// Simulate an abandoned builder (e.g. exception during deserialization)
440+
// by closing without calling build(). close() calls reset() then returns
441+
// the builder to the pool.
442+
}
443+
444+
// Reuse the pooled builder — it should be clean
445+
try (BTree.FastBuilder<Integer> builder = BTree.fastBuilder())
446+
{
447+
assertTrue("FastBuilder should be empty after reset, but savedBuffer/savedNextKey leaked",
448+
builder.validateEmpty());
449+
}
450+
}
451+
431452
/**
432453
* <code>UpdateFunction</code> that count the number of call made to apply for each value.
433454
*/

0 commit comments

Comments
 (0)