package org.jgroups.tests; import org.jgroups.Global; import org.jgroups.util.RequestTable; import org.jgroups.util.Util; import org.testng.annotations.Test; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.stream.LongStream; /** * Tests {@link RequestTable} * @author Bela Ban * @since 3.6.7 */ @Test(groups=Global.FUNCTIONAL) public class RequestTableTest { public void testSimpleCreation() { RequestTable<Integer> table=new RequestTable<>(100); assert table.size() == 0 && table.high() == 0 && table.low() == 0; assert table.capacity() == Util.getNextHigherPowerOfTwo(100); } public void testIndex() { RequestTable<Integer> table=new RequestTable<>(8); assert table.capacity() == 8; for(int i=0; i < 20; i++) { int index=table.index(i); int wrap_around=i % table.capacity(); String format=String.format("seqno=%d, index=%d, wrap_around=%d", i, index, wrap_around); System.out.println(format); assert index == wrap_around : format; } } public void testAdd() { RequestTable<Integer> table=create(4, 0, 4); System.out.println("table: " + table); assertBounds(table, 4, 4); } public void testAdd2() { RequestTable<Integer> table=create(4, 0, 4); remove(table, 0, 2); // removes 0, 1 add(table, 4,6); // adds 4, 5, wraps around assertBounds(table, 4, 4); } public void testAddWithOffset() { RequestTable<Integer> table=new RequestTable<>(8, 1, 1); add(table, 1, 8); System.out.println("table = " + table); assertBounds(table, 8, 7); add(table, 8, 12); System.out.println("table = " + table); assertBounds(table, 16, 11); remove(table, 0, 20); add(table, 12, 15); boolean rc=table.compact(); assert rc; assertBounds(table, 8, 3); } public void testAddAndGrow() { RequestTable<Integer> table=create(4, 0, 4); for(int i=4; i <= 10; i++) { long seqno=table.add(i); assert seqno == i; } System.out.println("table = " + table); assertBounds(table, Util.getNextHigherPowerOfTwo(11), 11); assert table.low() == 0 && table.high() == 11; } public void testAddAndGrow2() { RequestTable<Integer> table=create(4, 0, 4); remove(table, 0, 10); add(table, 4, 12); checkIndices(table, 4, 12); add(table, 12, 20); // grows array again checkIndices(table, 4, 20); table.add(20); // grows array checkIndices(table, 4, 21); } public void testAddRemove() { RequestTable<Integer> table=create(8, 0, 8); System.out.println("table = " + table); remove(table, 1, 8); System.out.println("table = " + table); table.add(8); // will trigger a growth System.out.println("table = " + table); assert table.size() == 2; // 0 and 8 assert table.capacity() == 16; add(table, 9, 100); System.out.println("table = " + table); assert table.size() == 93; assert table.capacity() == Util.getNextHigherPowerOfTwo(100); table.remove(0); boolean rc=table.compact(); assert !rc; remove(table, 8, 50); System.out.println("rc = " + rc); assert table.size() == 50; rc=table.compact(); System.out.println("table = " + table); assert rc; assert table.size() == 50; assert table.capacity() == 64; } public void testAddMany() { RequestTable<Integer> table=create(4, 0, 0); add(table, 0, 1000); // adds [0 .. 999] System.out.println("table = " + table); assertBounds(table, 1024, 1000); assert table.low() == 0 && table.high() == 1000; remove(table, 1, 1000); System.out.println("table = " + table); assertBounds(table, 1024, 1); assert table.low() == 0 && table.high() == 1000; table.remove(0); System.out.println("table = " + table); assertBounds(table, 1024, 0); assert table.low() == 1000 && table.high() == 1000; add(table, 1000, 2000); System.out.println("table = " + table); assertBounds(table, 1024, 1000); assert table.low() == 1000 && table.high() == 2000; remove(table, 800, 2200); System.out.println("table = " + table); assertBounds(table, 1024, 0); assert table.low() == 2000 && table.high() == 2000; } public void testAddAndRemove() { RequestTable<Integer> table=create(4, 0, 4); remove(table, 0, 3); assert table.low() == 3 && table.high() == 4; add(table, 4, 7); // adds 4,5,6 assertBounds(table, 4, 4); assert table.low() == 3 && table.high() == 7; table.add(7); // this grows the array to 8 assertBounds(table, 8, 5); assert table.low() == 3 && table.high() == 8; checkIndices(table, table.low(), table.high()); } public void testRemove() { RequestTable<Integer> table=new RequestTable<>(1); Integer el=table.remove(0); assert el == null; } public void testRemoveOne() { RequestTable<Integer> table=create(1, 0, 1); assertBounds(table, 1, 1); Integer el=table.remove(0); assert el != null && el == 0; assert table.low() == 1 && table.high() == 1; } public void testRemoveAll() { RequestTable<Integer> table=create(4, 0, 4); remove(table, 1, 3); assertBounds(table, 4, 2); assert table.low() == 0 && table.high() == 4; table.remove(0); assertBounds(table, 4, 1); assert table.low() == 3 && table.high() == 4; table.remove(3); assertBounds(table, 4, 0); assert table.low() == 4 && table.high() == 4; } public void testRemoveMany() { RequestTable<Integer> table=create(40, 0, 41); // exclusive 41 remove(table, 0, 4); table.removeMany(LongStream.rangeClosed(4, 40), seqno -> System.out.printf("seqno=%d\n", seqno)); assert table.size() == 0; assert table.low() == 41 && table.high() == 41; } public void testClear() { RequestTable<Integer> table=create(4, 0, 0); add(table, 0, 100); assertBounds(table, 128, 100); remove(table, 0, 50); assert table.low() == 50 && table.high() == 100; table.clear(); assert table.low() == 0 && table.high() == 0; assert table.size() == 0; } public void testConcurrentRemoval() throws Exception { RequestTable<Integer> table=create(4, 0, 1000); List<Integer> tmp=new ArrayList<>(1000); for(int i=0; i < 1000; i++) tmp.add(i); Collections.shuffle(tmp); ConcurrentLinkedQueue<Integer> list=new ConcurrentLinkedQueue<>(tmp); Remover[] removers=new Remover[10]; final CountDownLatch latch=new CountDownLatch(1); for(int i=0; i < removers.length; i++) { removers[i]=new Remover(table, latch, list); removers[i].start(); } int total_removed=0; latch.countDown(); for(Remover remover: removers) { remover.join(); total_removed+=remover.num_removed; } System.out.printf("table: %s, total removed=%d\n", table, total_removed); assertBounds(table, 1024, 0); assert table.low() == 1000 && table.high() == 1000; assert total_removed == 1000; } public void testGrow() { RequestTable<Integer> table=create(4, 0, 4); assert table.capacity() == 4; table.grow(5); assert table.capacity() == 8; checkIndices(table, 0, 4); } public void testCompact() { RequestTable<Integer> table=create(8, 0, 4); boolean rc=table.compact(); assert rc; assertBounds(table, 4, 4); rc=table.compact(); assert !rc; remove(table, 0, 3); rc=table.compact(); assert rc; assertBounds(table, 2, 1); } public void testCompact2() { RequestTable<Integer> table=create(8, 0, 8); remove(table, 0, 6); // remove [0 .. 5], 6 and 7 are still present add(table, 8, 10); // wraps around for 8, 9 boolean rc=table.compact(); assert rc; assertBounds(table, 4, 4); checkIndices(table, table.low(), table.high()); } public void testCompact3() { RequestTable<Integer> table=create(8, 0, 8); remove(table, 1,5); boolean rc=table.compact(); assert !rc; checkIndices(table, 8,4); } public void testCompact4() { RequestTable<Integer> table=new RequestTable<>(1024, 1, 1); add(table, 1, 1023); table.remove(1); // low=2 remove(table, 3, 1002); System.out.println("table = " + table); assert table.get(2) == 2; for(int i=1002; i < 1023; i++) assert table.get(i) == i : String.format("expected %d but got %d", i, table.get(i)); boolean rc=table.compact(); assert !rc; assertBounds(table, 1024, 22); assert table.get(2) == 2; for(int i=1002; i < 1023; i++) assert table.get(i) == i : String.format("expected %d but got %d", i, table.get(i)); table.add(1023); assertBounds(table, 1024, 23); assert table.get(2) == 2; for(int i=1002; i < 1024; i++) assert table.get(i) == i : String.format("expected %d but got %d", i, table.get(i)); } public void testContiguousSpaceAvailable() { RequestTable<Integer> table=create(8, 0, 8); boolean rc=table.contiguousSpaceAvailable(); assert !rc; remove(table, 5,8); rc=table.contiguousSpaceAvailable(); assert !rc; table.remove(1); table.remove(4); rc=table.contiguousSpaceAvailable(); assert rc; rc=table.compact(); assert !rc; } public void testContiguousSpaceAvailable2() { RequestTable<Integer> table=create(8, 0, 8); boolean rc=table.contiguousSpaceAvailable(); assert !rc; remove(table, 6,8); rc=table.contiguousSpaceAvailable(); assert !rc; remove(table, 0, 3); rc=table.contiguousSpaceAvailable(); assert !rc; } public void testCompactToZeroAndSubsequentAddition() { RequestTable<Integer> table=create(8, 0, 8); remove(table, 0, 10); assert table.size() == 0; while(table.compact()) ; add(table, 8, 20); assertBounds(table, 16, 12); for(int i=8; i < 20; i++) { Integer num=table.remove(i); assert num != null && num == i; } assertBounds(table, 16, 0); } public void testCompactTriggeredByRemoves() { RequestTable<Integer> table=create(4, 0, 0); add(table, 0, 10000); remove(table, 0, 9000); System.out.println("table = " + table); table.removesTillCompaction(10); int cap=table.capacity(); assert cap == Util.getNextHigherPowerOfTwo(10000); remove(table, 9000, 9012); cap/=2; assert table.capacity() == Util.getNextHigherPowerOfTwo(cap); remove(table, 9000, 10000); assert table.size() == 0; while(table.compact()) ; assertBounds(table, 0, 0); System.out.println("table = " + table); add(table, 10000, 10010); System.out.println("table = " + table); assertBounds(table, 16, 10); } public void testMultipleGrowAndCompact() { RequestTable<Integer> table=create(100, 0, 100); remove(table, 0, 90); add(table, 100, 110); for(int i=1; i <= 4; i++) { System.out.printf("#%d: current capacity: %d", i, table.capacity()); table.grow(table.capacity() + 1); System.out.printf(", new capacity: %d\n", table.capacity()); } System.out.println("Compacting table:"); while(true) { int old_cap=table.capacity(); boolean rc=table.compact(); if(!rc) break; System.out.printf("Compacted table from %d -> %d\n", old_cap, table.capacity()); System.out.print("Checking contents: "); checkIndices(table, table.low(), table.high()); System.out.println("OK"); } } public void testMultipleGrowAndCompact2() { RequestTable<Integer> table=create(100, 0, 100); remove(table, 10, 90); assertBounds(table, 128, 20); checkIndices(table, 0, 10); checkIndices(table, 90, 99); boolean rc=table.compact(); assert !rc; assertBounds(table, 128, 20); checkIndices(table, 0, 10); checkIndices(table, 90, 99); } public void testForEach() { RequestTable<Integer> table=create(100, 0, 100); for(int i=0; i < 100; i++) if(i % 2 == 0) // removes odd elements table.remove(i); CountNonNullElements counter=new CountNonNullElements(); table.forEach(counter); int num=counter.num; assert num == 50 : String.format("expected 50 but got %d non-null elements", num); } public void testForEach2() { RequestTable<Integer> table=create(100, 0, 100); remove(table, 10, 90); boolean rc=table.compact(); assert !rc; CountNonNullElements counter=new CountNonNullElements(); table.forEach(counter); int num=counter.num; assert num == 20 : String.format("expected 20 but got %d non-null elements", num); } public void testForEach3() { RequestTable<Integer> table=create(60, 0, 60); remove(table, 0, 50); add(table, 60, 70); boolean rc=table.compact(); assert rc; CountNonNullElements counter=new CountNonNullElements(); table.forEach(counter); int num=counter.num; assert num == 20 : String.format("expected 20 but got %d non-null elements", num); checkIndices(table, table.low(), table.high()); } public void testForEach4() { RequestTable<Integer> table=create(4, 0, 4); CountNonNullElements counter=new CountNonNullElements(); table.forEach(counter); int num=counter.num; assert num == 4 : String.format("expected 4 but got %d non-null elements", num); } protected static RequestTable<Integer> create(int capacity, int from, int to) { RequestTable<Integer> table=new RequestTable<>(capacity); add(table, from, to); return table; } protected static void add(RequestTable<Integer> table, int from, int to) { for(int i=from; i < to; i++) table.add(i); } protected static void remove(RequestTable<Integer> table, int from, int to) { for(int i=from; i < to; i++) table.remove(i); } protected static <T> void assertBounds(RequestTable<T> table, int capacity, int size) { if(capacity >= 0) assert table.capacity() == capacity : String.format("table.cap=%d, expected cap=%d", table.capacity(), capacity); if(size >= 0) assert table.size() == size : String.format("table.size=%d, expected size=%d", table.size(), size); } protected static void checkIndices(RequestTable<Integer> table, long low, long high) { for(long i=low; i < high; i++) { Integer num=table.get(i); System.out.printf("%d: %d\n", i, num); assert num != null && num == i : String.format("expected %d but got %d", i, num); } } protected static class Remover extends Thread { protected final RequestTable<Integer> table; protected final CountDownLatch latch; protected final Queue<Integer> list; protected int num_removed; public Remover(RequestTable<Integer> table, CountDownLatch latch, Queue<Integer> list) { this.table=table; this.latch=latch; this.list=list; } public void run() { try { latch.await(); } catch(InterruptedException e) { e.printStackTrace(); } Integer seqno=null; while((seqno=list.poll()) != null) { Integer el=table.remove(seqno); if(el != null) num_removed++; } System.out.printf("Thread %s: removed %d element(s)\n", Thread.currentThread().getId(), num_removed); } } protected static class CountNonNullElements implements RequestTable.Visitor<Integer> { protected int num; public boolean visit(Integer element) { if(element != null) num++; return true; } } protected static class Counter extends CountNonNullElements { public boolean visit(Integer element) { num++; return true; } } }