use of org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.GenericCallbackFuture in project bookkeeper by apache.
the class Etcd64bitIdGeneratorTest method testGenerateIdSequence.
@Test
public void testGenerateIdSequence() throws Exception {
Map<Integer, Long> buckets = new HashMap<>();
int numIterations = 10;
for (int i = 0; i < numIterations; i++) {
log.info("Id generation iteration : {}", i);
for (int j = 0; j < Etcd64bitIdGenerator.NUM_BUCKETS; j++) {
GenericCallbackFuture<Long> future = new GenericCallbackFuture<>();
generator.generateLedgerId(future);
long lid = future.get();
int bucketId = Etcd64bitIdGenerator.getBucketId(lid);
long idInBucket = Etcd64bitIdGenerator.getIdInBucket(lid);
Long prevIdInBucket = buckets.put(bucketId, idInBucket);
if (null == prevIdInBucket) {
assertEquals(1, idInBucket);
} else {
assertEquals(prevIdInBucket + 1, idInBucket);
}
}
}
assertEquals(Etcd64bitIdGenerator.NUM_BUCKETS, buckets.size());
for (Map.Entry<Integer, Long> bucketEntry : buckets.entrySet()) {
assertEquals(numIterations, bucketEntry.getValue().intValue());
}
}
use of org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.GenericCallbackFuture in project bookkeeper by apache.
the class LedgerRecovery2Test method testFirstWriterCannotCommitWriteAfter2ndWriterCloses.
/**
* This test verifies the fix for the data loss scenario found by the TLA+ specfication, specifically
* the invariant violation that metadata and writer can diverge. The scenario is that the original writer
* can commit an entry e that will later be lost because a second writer can close the ledger at e-1.
* The cause is that fencing was originally only performed on LAC reads which is not enough to prevent
* the 1st writer from reaching Ack Quorum after the 2nd writer has closed the ledger. The fix has
* been to fence on recovery reads also.
*/
@Test
public void testFirstWriterCannotCommitWriteAfter2ndWriterCloses() throws Exception {
/*
This test uses CompletableFutures to control the sequence of actions performed by
two writers. There are different sets of futures:
- block*: These futures block the various reads, writes and metadata updates until the
test thread is ready for them to be executed. Thus ensuring the right sequence
of events occur.
- reachedStepN: These futures block in the test thread to ensure that we only unblock
an action when the prior one has been executed and we are already blocked
on the next actionin the sequence.
*/
// Setup w1
CompletableFuture<Void> reachedStep1 = new CompletableFuture<>();
CompletableFuture<Void> reachedStep2 = new CompletableFuture<>();
CompletableFuture<Void> reachedStep3 = new CompletableFuture<>();
CompletableFuture<Void> reachedStep4 = new CompletableFuture<>();
CompletableFuture<Void> reachedStep5 = new CompletableFuture<>();
CompletableFuture<Void> reachedStep6 = new CompletableFuture<>();
CompletableFuture<Void> reachedStep7 = new CompletableFuture<>();
CompletableFuture<Void> reachedStep8 = new CompletableFuture<>();
CompletableFuture<Void> reachedStep9 = new CompletableFuture<>();
MockBookies mockBookies = new MockBookies();
MockClientContext clientCtx1 = MockClientContext.create(mockBookies);
Versioned<LedgerMetadata> md1 = setupLedger(clientCtx1, 1, Lists.newArrayList(b1, b2, b3));
CompletableFuture<Void> blockB1Write = new CompletableFuture<>();
CompletableFuture<Void> blockB2Write = new CompletableFuture<>();
CompletableFuture<Void> blockB3Write = new CompletableFuture<>();
clientCtx1.getMockBookieClient().setPreWriteHook((bookie, ledgerId, entryId) -> {
// ignore seed entries e0 and e1
if (entryId < 2) {
return FutureUtils.value(null);
}
if (!reachedStep1.isDone()) {
reachedStep1.complete(null);
}
if (bookie.equals(b1)) {
return blockB1Write;
} else if (bookie.equals(b2)) {
reachedStep9.complete(null);
return blockB2Write;
} else if (bookie.equals(b3)) {
reachedStep3.complete(null);
return blockB3Write;
} else {
return FutureUtils.value(null);
}
});
LedgerHandle w1 = new LedgerHandle(clientCtx1, 1, md1, BookKeeper.DigestType.CRC32C, ClientUtil.PASSWD, WriteFlag.NONE);
w1.addEntry("e0".getBytes(StandardCharsets.UTF_8));
w1.addEntry("e1".getBytes(StandardCharsets.UTF_8));
// Setup w2
MockClientContext clientCtx2 = MockClientContext.create(mockBookies);
Versioned<LedgerMetadata> md2 = setupLedger(clientCtx2, 1, Lists.newArrayList(b1, b2, b3));
CompletableFuture<Void> blockB1ReadLac = new CompletableFuture<>();
CompletableFuture<Void> blockB2ReadLac = new CompletableFuture<>();
CompletableFuture<Void> blockB3ReadLac = new CompletableFuture<>();
CompletableFuture<Void> blockB1ReadEntry0 = new CompletableFuture<>();
CompletableFuture<Void> blockB2ReadEntry0 = new CompletableFuture<>();
CompletableFuture<Void> blockB3ReadEntry0 = new CompletableFuture<>();
AtomicBoolean isB1LacRead = new AtomicBoolean(true);
AtomicBoolean isB2LacRead = new AtomicBoolean(true);
AtomicBoolean isB3LacRead = new AtomicBoolean(true);
clientCtx2.getMockBookieClient().setPreReadHook((bookie, ledgerId, entryId) -> {
if (bookie.equals(b1)) {
if (isB1LacRead.get()) {
isB1LacRead.set(false);
reachedStep2.complete(null);
return blockB1ReadLac;
} else {
reachedStep6.complete(null);
return blockB1ReadEntry0;
}
} else if (bookie.equals(b2)) {
if (isB2LacRead.get()) {
try {
isB2LacRead.set(false);
reachedStep4.complete(null);
// block this read - it does not succeed
blockB2ReadLac.get();
} catch (Throwable t) {
}
return FutureUtils.exception(new BKException.BKWriteException());
} else {
reachedStep7.complete(null);
return blockB2ReadEntry0;
}
} else if (bookie.equals(b3)) {
if (isB3LacRead.get()) {
isB3LacRead.set(false);
reachedStep5.complete(null);
return blockB3ReadLac;
} else {
return blockB3ReadEntry0;
}
} else {
return FutureUtils.value(null);
}
});
AtomicInteger w2MetaUpdates = new AtomicInteger(0);
CompletableFuture<Void> blockW2StartingRecovery = new CompletableFuture<>();
CompletableFuture<Void> blockW2ClosingLedger = new CompletableFuture<>();
clientCtx2.getMockLedgerManager().setPreWriteHook((ledgerId, metadata) -> {
if (w2MetaUpdates.get() == 0) {
w2MetaUpdates.incrementAndGet();
return blockW2StartingRecovery;
} else {
reachedStep8.complete(null);
return blockW2ClosingLedger;
}
});
ReadOnlyLedgerHandle w2 = new ReadOnlyLedgerHandle(clientCtx2, 1L, md2, BookKeeper.DigestType.CRC32C, PASSWD, false);
// Start an async add entry, blocked for now.
CompletableFuture<Object> w1WriteFuture = new CompletableFuture<>();
AtomicInteger writeResult = new AtomicInteger(0);
w1.asyncAddEntry("e2".getBytes(), (int rc, LedgerHandle lh1, long entryId, Object ctx) -> {
if (rc == BKException.Code.OK) {
writeResult.set(1);
} else {
writeResult.set(2);
}
SyncCallbackUtils.finish(rc, null, w1WriteFuture);
}, null);
// Step 1. w2 starts recovery
stepBlock(reachedStep1);
GenericCallbackFuture<Void> recoveryPromise = new GenericCallbackFuture<>();
w2.recover(recoveryPromise, null, false);
blockW2StartingRecovery.complete(null);
// Step 2. w2 fencing read LAC reaches B1
stepBlock(reachedStep2);
blockB1ReadLac.complete(null);
// Step 3. w1 add e0 reaches B3
stepBlock(reachedStep3);
blockB3Write.complete(null);
// Step 4. w2 fencing LAC read does not reach B2 or it fails
stepBlock(reachedStep4);
blockB2ReadLac.complete(null);
// Step 5. w2 fencing LAC read reaches B3
stepBlock(reachedStep5);
blockB3ReadLac.complete(null);
// Step 6. w2 sends read e0 to b1, gets NoSuchLedger
stepBlock(reachedStep6);
blockB1ReadEntry0.complete(null);
// Step 7. w2 send read e0 to b2, gets NoSuchLedger
stepBlock(reachedStep7);
blockB2ReadEntry0.complete(null);
// Step 8. w2 closes ledger because (Qw-Qa)+1 bookies confirmed they do not have it
// last entry id set to 0
stepBlock(reachedStep8);
blockW2ClosingLedger.complete(null);
// Step 9. w1 add e0 reaches b2 (which was fenced by a recovery read)
stepBlock(reachedStep9);
blockB2Write.complete(null);
// Step 10. w1 write fails to reach AckQuorum
try {
w1WriteFuture.get(200, TimeUnit.MILLISECONDS);
Assert.fail("The write to b2 should have failed as it was fenced by the recovery read of step 7");
} catch (ExecutionException e) {
Assert.assertTrue(e.getCause() instanceof BKException.BKLedgerFencedException);
}
// w1 received negative acknowledgement of e2 being written
Assert.assertEquals(1, w1.getLedgerMetadata().getAllEnsembles().size());
Assert.assertEquals(2, writeResult.get());
Assert.assertEquals(1L, w1.getLastAddConfirmed());
// w2 closed the ledger with only the original entries, not the third one
// i.e there is no divergence between w1m, w2 and metadata
Assert.assertEquals(1, w2.getLedgerMetadata().getAllEnsembles().size());
Assert.assertEquals(1L, w2.getLastAddConfirmed());
}
use of org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.GenericCallbackFuture in project bookkeeper by apache.
the class Etcd64bitIdGeneratorTest method testGenerateIdParallel.
/**
* Test generating id in parallel and ensure there is no duplicated id.
*/
@Test
public void testGenerateIdParallel() throws Exception {
final int numThreads = 10;
@Cleanup("shutdown") ExecutorService executor = Executors.newFixedThreadPool(numThreads);
final int numIds = 10000;
final AtomicLong totalIds = new AtomicLong(numIds);
final Set<Long> ids = Collections.newSetFromMap(new ConcurrentHashMap<>());
final RateLimiter limiter = RateLimiter.create(1000);
final CompletableFuture<Void> doneFuture = new CompletableFuture<>();
for (int i = 0; i < numThreads; i++) {
executor.submit(() -> {
Client client = Client.builder().endpoints(etcdContainer.getClientEndpoint()).build();
Etcd64bitIdGenerator gen = new Etcd64bitIdGenerator(client.getKVClient(), scope);
AtomicBoolean running = new AtomicBoolean(true);
while (running.get()) {
limiter.acquire();
GenericCallbackFuture<Long> genFuture = new GenericCallbackFuture<>();
gen.generateLedgerId(genFuture);
genFuture.thenAccept(lid -> {
boolean duplicatedFound = !(ids.add(lid));
if (duplicatedFound) {
running.set(false);
doneFuture.completeExceptionally(new IllegalStateException("Duplicated id " + lid + " generated : " + ids));
return;
} else {
if (totalIds.decrementAndGet() <= 0) {
running.set(false);
doneFuture.complete(null);
}
}
}).exceptionally(cause -> {
running.set(false);
doneFuture.completeExceptionally(cause);
return null;
});
}
});
}
FutureUtils.result(doneFuture);
assertTrue(totalIds.get() <= 0);
assertTrue(ids.size() >= numIds);
}
Aggregations