/* * Copyright 2016-2020 Chronicle Software * * https://chronicle.software * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.openhft.chronicle.queue.impl.single; import net.openhft.chronicle.bytes.*; import net.openhft.chronicle.core.Jvm; import net.openhft.chronicle.core.OS; import net.openhft.chronicle.core.annotation.UsedViaReflection; import net.openhft.chronicle.core.io.AbstractCloseable; import net.openhft.chronicle.core.time.SetTimeProvider; import net.openhft.chronicle.core.time.TimeProvider; import net.openhft.chronicle.core.util.StringUtils; import net.openhft.chronicle.queue.*; import net.openhft.chronicle.queue.impl.RollingChronicleQueue; import net.openhft.chronicle.threads.NamedThreadFactory; import net.openhft.chronicle.wire.*; import org.jetbrains.annotations.NotNull; import org.junit.Assert; import org.junit.Assume; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import java.io.*; import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.time.Instant; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.Supplier; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static net.openhft.chronicle.core.io.Closeable.closeQuietly; import static net.openhft.chronicle.queue.RollCycles.*; import static net.openhft.chronicle.queue.impl.single.SingleChronicleQueue.QUEUE_METADATA_FILE; import static net.openhft.chronicle.queue.impl.single.SingleChronicleQueue.SUFFIX; import static org.junit.Assert.*; import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; @RunWith(Parameterized.class) public class SingleChronicleQueueTest extends ChronicleQueueTestBase { private static final long TIMES = (4L << 20L); @NotNull protected final WireType wireType; protected final boolean encryption; // ************************************************************************* // // TESTS // // ************************************************************************* public SingleChronicleQueueTest(@NotNull WireType wireType, boolean encryption) { this.wireType = wireType; this.encryption = encryption; } @Parameters(name = "wireType={0}, encrypted={1}") public static Collection<Object[]> data() { return Arrays.asList(// {WireType.TEXT}, new Object[]{WireType.BINARY, false}, new Object[]{WireType.BINARY_LIGHT, false}, new Object[]{WireType.COMPRESSED_BINARY, false} // {WireType.DELTA_BINARY} // {WireType.FIELDLESS_BINARY} ); } private static List<String> getMappedQueueFileCount() throws IOException, InterruptedException { final int processId = OS.getProcessId(); final List<String> fileList = new ArrayList<>(); final Process pmap = new ProcessBuilder("pmap", Integer.toString(processId)).start(); pmap.waitFor(); try (final BufferedReader reader = new BufferedReader(new InputStreamReader(pmap.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { if (line.contains(SUFFIX)) { fileList.add(line); } } } return fileList; } private static long countEntries(final ChronicleQueue queue) { final ExcerptTailer tailer = queue.createTailer(); tailer.toStart().direction(TailerDirection.FORWARD); long entryCount = 0L; while (true) { try (final DocumentContext ctx = tailer.readingDocument()) { if (!ctx.isPresent()) { break; } entryCount++; } } return entryCount; } private static void waitFor(final Supplier<Boolean> condition, final String message) { final long timeoutAt = System.currentTimeMillis() + 10_000L; while (System.currentTimeMillis() < timeoutAt) { if (condition.get()) { return; } } fail(message); } @NotNull private static Object[] testConfiguration(final WireType binary, final boolean encrypted) { return new Object[]{binary.name() + " - " + (encrypted ? "" : "not ") + "encrypted", binary, encrypted}; } @Test public void testAppend() { try (final ChronicleQueue queue = builder(getTmpDir(), wireType) .build()) { final ExcerptAppender appender = queue.acquireAppender(); for (int i = 0; i < 10; i++) { final int n = i; appender.writeDocument(w -> w.write(TestKey.test).int32(n)); assertEquals(n, queue.rollCycle().toSequenceNumber(appender.lastIndexAppended())); } assertEquals(10L, countEntries(queue)); } } @Test public void testTextReadWrite() { File tmpDir = getTmpDir(); try (final ChronicleQueue queue = builder(tmpDir, wireType) .build()) { queue.acquireAppender().writeText("hello world"); assertEquals("hello world", queue.createTailer().readText()); } } @Test public void testCleanupDir() { File tmpDir = getTmpDir(); try (final ChronicleQueue queue = builder(tmpDir, wireType) .build()) { final ExcerptAppender appender = queue.acquireAppender(); try (DocumentContext dc = appender.writingDocument()) { dc.wire().write("hello").text("world"); } } DirectoryUtils.deleteDir(tmpDir); if (OS.isWindows()) { System.err.println("#460 Directory clean up not supported on Windows"); } else { assertFalse(tmpDir.exists()); } } @Test public void testRollbackOnAppend() { try (final ChronicleQueue queue = builder(getTmpDir(), wireType) .build()) { final ExcerptAppender appender = queue.acquireAppender(); try (DocumentContext dc = appender.writingDocument()) { dc.wire().write("hello").text("world"); } try (DocumentContext dc = appender.writingDocument()) { dc.wire().write("hello").text("world2"); } ExcerptTailer tailer = queue.createTailer(); try (DocumentContext dc = tailer.readingDocument()) { dc.wire().read("hello"); dc.rollbackOnClose(); } try (DocumentContext dc = tailer.readingDocument()) { assertEquals("world", dc.wire().read("hello").text()); } try (DocumentContext dc = tailer.readingDocument()) { assertEquals("world2", dc.wire().read("hello").text()); } } } @Test public void testWriteWithDocumentReadBytesDifferentThreads() throws InterruptedException, TimeoutException, ExecutionException { try (final ChronicleQueue queue = builder(getTmpDir(), wireType) .build()) { final String expected = "some long message"; ExecutorService service1 = Executors.newSingleThreadExecutor( new NamedThreadFactory("service1")); ScheduledExecutorService service2 = null; try { Future f = service1.submit(() -> { try (final ExcerptAppender appender = queue.acquireAppender()) { try (final DocumentContext dc = appender.writingDocument()) { dc.wire().writeEventName(() -> "key").text(expected); } } }); BlockingQueue<Bytes> result = new ArrayBlockingQueue<>(10); service2 = Executors.newSingleThreadScheduledExecutor( new NamedThreadFactory("service2")); service2.scheduleAtFixedRate(() -> { Bytes b = Bytes.allocateElasticOnHeap(128); final ExcerptTailer tailer = queue.createTailer(); tailer.readBytes(b); if (b.readRemaining() == 0) return; b.readPosition(0); result.add(b); throw new RejectedExecutionException(); }, 1, 1, TimeUnit.MICROSECONDS); final Bytes bytes = result.poll(5, TimeUnit.SECONDS); if (bytes == null) { // troubleshoot failed test http://teamcity.higherfrequencytrading.com:8111/viewLog.html?buildId=264141&tab=buildResultsDiv&buildTypeId=OpenHFT_ChronicleQueue4_Snapshothttp://teamcity.higherfrequencytrading.com:8111/viewLog.html?buildId=264141&tab=buildResultsDiv&buildTypeId=OpenHFT_ChronicleQueue4_Snapshothttp://teamcity.higherfrequencytrading.com:8111/viewLog.html?buildId=264141&tab=buildResultsDiv&buildTypeId=OpenHFT_ChronicleQueue4_Snapshothttp://teamcity.higherfrequencytrading.com:8111/viewLog.html?buildId=264141&tab=buildResultsDiv&buildTypeId=OpenHFT_ChronicleQueue4_Snapshothttp://teamcity.higherfrequencytrading.com:8111/viewLog.html?buildId=264141&tab=buildResultsDiv&buildTypeId=OpenHFT_ChronicleQueue4_Snapshothttp://teamcity.higherfrequencytrading.com:8111/viewLog.html?buildId=264141&tab=buildResultsDiv&buildTypeId=OpenHFT_ChronicleQueue4_Snapshothttp://teamcity.higherfrequencytrading.com:8111/viewLog.html?buildId=264141&tab=buildResultsDiv&buildTypeId=OpenHFT_ChronicleQueue4_Snapshothttp://teamcity.higherfrequencytrading.com:8111/viewLog.html?buildId=264141&tab=buildResultsDiv&buildTypeId=OpenHFT_ChronicleQueue4_Snapshothttp://teamcity.higherfrequencytrading.com:8111/viewLog.html?buildId=264141&tab=buildResultsDiv&buildTypeId=OpenHFT_ChronicleQueue4_Snapshothttp://teamcity.higherfrequencytrading.com:8111/viewLog.html?buildId=264141&tab=buildResultsDiv&buildTypeId=OpenHFT_ChronicleQueue4_Snapshothttp://teamcity.higherfrequencytrading.com:8111/viewLog.html?buildId=264141&tab=buildResultsDiv&buildTypeId=OpenHFT_ChronicleQueue4_Snapshothttp://teamcity.higherfrequencytrading.com:8111/viewLog.html?buildId=264141&tab=buildResultsDiv&buildTypeId=OpenHFT_ChronicleQueue4_Snapshot f.get(1, TimeUnit.SECONDS); throw new NullPointerException("nothing in result"); } try { final String actual = this.wireType.apply(bytes).read(() -> "key").text(); assertEquals(expected, actual); f.get(1, TimeUnit.SECONDS); } finally { bytes.releaseLast(); } } finally { service1.shutdownNow(); if (service2 != null) service2.shutdownNow(); } } } @Test(expected = IllegalStateException.class) public void shouldBlowUpIfTryingToCreateQueueWithUnparseableRollCycle() { File tmpDir = getTmpDir(); try (final ChronicleQueue queue = builder(tmpDir, wireType).rollCycle(new RollCycleDefaultingTest.MyRollcycle()).build()) { try (DocumentContext documentContext = queue.acquireAppender().writingDocument()) { documentContext.wire().write("somekey").text("somevalue"); } } try (final ChronicleQueue ignored = builder(tmpDir, wireType).rollCycle(HOURLY).build()) { } } @Test public void shouldNotBlowUpIfTryingToCreateQueueWithIncorrectRollCycle() { File tmpDir = getTmpDir(); try (final ChronicleQueue queue = builder(tmpDir, wireType).rollCycle(DAILY).build()) { try (DocumentContext documentContext = queue.acquireAppender().writingDocument()) { documentContext.wire().write("somekey").text("somevalue"); } } // we don't store which RollCycles enum was used and we try and match by format string, we // match the first RollCycles with the same format string, which may not // be the RollCycles it was written with try (final ChronicleQueue ignored = builder(tmpDir, wireType).rollCycle(HOURLY).build()) { assertEquals(DAILY, ignored.rollCycle()); } } @Test public void shouldOverrideDifferentEpoch() { File tmpDir = getTmpDir(); try (final ChronicleQueue queue = builder(tmpDir, wireType).rollCycle(TEST_SECONDLY).epoch(100).build()) { try (DocumentContext documentContext = queue.acquireAppender().writingDocument()) { documentContext.wire().write("somekey").text("somevalue"); } } try (final ChronicleQueue ignored = builder(tmpDir, wireType).rollCycle(TEST_SECONDLY).epoch(10).build()) { assertEquals(100, ((SingleChronicleQueue) ignored).epoch()); } } @Test public void testReadWriteHourly() { File tmpDir = getTmpDir(); try (final ChronicleQueue qAppender = builder(tmpDir, wireType).rollCycle(HOURLY).build()) { try (DocumentContext documentContext = qAppender.acquireAppender().writingDocument()) { documentContext.wire().write("somekey").text("somevalue"); } } try (final ChronicleQueue qTailer = builder(tmpDir, wireType).rollCycle(HOURLY).build()) { try (DocumentContext documentContext2 = qTailer.createTailer().readingDocument()) { String str = documentContext2.wire().read("somekey").text(); assertEquals("somevalue", str); } } } @Test public void testMetaIndexTest() { File tmpDir = getTmpDir(); try (final ChronicleQueue q = builder(tmpDir, wireType).rollCycle(HOURLY).build()) { { ExcerptAppender appender = q.acquireAppender(); try (DocumentContext documentContext = appender.writingDocument()) { documentContext.wire().getValueOut().text("one"); } try (DocumentContext documentContext = appender.writingDocument()) { documentContext.wire().getValueOut().text("two"); } try (DocumentContext documentContext = appender.writingDocument(true)) { documentContext.wire().getValueOut().text("meta1"); } try (DocumentContext documentContext = appender.writingDocument()) { documentContext.wire().getValueOut().text("three"); } try (DocumentContext documentContext = appender.writingDocument(true)) { documentContext.wire().getValueOut().text("meta2"); } try (DocumentContext documentContext = appender.writingDocument(true)) { documentContext.wire().getValueOut().text("meta3"); } try (DocumentContext documentContext = appender.writingDocument()) { documentContext.wire().getValueOut().text("four"); } } { ExcerptTailer tailer = q.createTailer(); try (DocumentContext documentContext2 = tailer.readingDocument()) { assertEquals(0, toSeq(q, documentContext2.index())); assertFalse(documentContext2.isMetaData()); assertEquals("one", documentContext2.wire().getValueIn().text()); } try (DocumentContext documentContext2 = tailer.readingDocument(true)) { assertEquals(1, toSeq(q, documentContext2.index())); assertFalse(documentContext2.isMetaData()); assertEquals("two", documentContext2.wire().getValueIn().text()); } try (DocumentContext documentContext2 = tailer.readingDocument(true)) { assertEquals(2, toSeq(q, documentContext2.index())); assertTrue(documentContext2.isMetaData()); assertEquals("meta1", documentContext2.wire().getValueIn().text()); } try (DocumentContext documentContext2 = tailer.readingDocument(true)) { assertEquals(2, toSeq(q, documentContext2.index())); assertFalse(documentContext2.isMetaData()); assertEquals("three", documentContext2.wire().getValueIn().text()); } try (DocumentContext documentContext2 = tailer.readingDocument(true)) { assertEquals(3, toSeq(q, documentContext2.index())); assertTrue(documentContext2.isMetaData()); assertEquals("meta2", documentContext2.wire().getValueIn().text()); } try (DocumentContext documentContext2 = tailer.readingDocument(true)) { assertEquals(3, toSeq(q, documentContext2.index())); assertTrue(documentContext2.isMetaData()); assertEquals("meta3", documentContext2.wire().getValueIn().text()); } try (DocumentContext documentContext2 = tailer.readingDocument(true)) { assertEquals(3, toSeq(q, documentContext2.index())); assertFalse(documentContext2.isMetaData()); assertEquals("four", documentContext2.wire().getValueIn().text()); } } { ExcerptTailer tailer = q.createTailer(); try (DocumentContext documentContext2 = tailer.readingDocument()) { assertEquals(0, toSeq(q, documentContext2.index())); assertFalse(documentContext2.isMetaData()); assertEquals("one", documentContext2.wire().getValueIn().text()); } try (DocumentContext documentContext2 = tailer.readingDocument(false)) { assertEquals(1, toSeq(q, documentContext2.index())); assertFalse(documentContext2.isMetaData()); assertEquals("two", documentContext2.wire().getValueIn().text()); } try (DocumentContext documentContext2 = tailer.readingDocument(false)) { assertEquals(2, toSeq(q, documentContext2.index())); assertFalse(documentContext2.isMetaData()); assertEquals("three", documentContext2.wire().getValueIn().text()); } } } } private long toSeq(final ChronicleQueue q, final long index) { return q.rollCycle().toSequenceNumber(index); } @Test public void testLastWritten() throws InterruptedException { // TODO FIX AbstractCloseable.disableCloseableTracing(); ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor( new NamedThreadFactory("test")); try { final SetTimeProvider tp = new SetTimeProvider(); try (ChronicleQueue outQueue = builder(getTmpDir(), wireType).rollCycle(RollCycles.TEST_SECONDLY).sourceId(1).timeProvider(tp).build()) { File inQueueTmpDir = getTmpDir(); try (ChronicleQueue inQueue = builder(inQueueTmpDir, wireType).rollCycle(RollCycles.TEST_SECONDLY).sourceId(2).timeProvider(tp).build()) { // write some initial data to the inqueue final Msg msg = inQueue.acquireAppender().methodWriterBuilder(Msg.class).recordHistory(true).get(); msg.msg("somedata-0"); assertEquals(1, inQueueTmpDir.listFiles(file -> file.getName().endsWith("cq4")).length); tp.advanceMillis(1000); // write data into the inQueue msg.msg("somedata-1"); assertEquals(2, inQueueTmpDir.listFiles(file -> file.getName().endsWith("cq4")).length); // read a message on the in queue and write it to the out queue { Msg out = outQueue.acquireAppender().methodWriterBuilder(Msg.class).recordHistory(true).get(); MethodReader methodReader = inQueue.createTailer().methodReader((Msg) out::msg); // reads the somedata-0 methodReader.readOne(); // reads the somedata-1 methodReader.readOne(); assertFalse(methodReader.readOne()); tp.advanceMillis(1000); assertFalse(methodReader.readOne()); } assertEquals("trying to read should not create a file", 2, inQueueTmpDir.listFiles(file -> file.getName().endsWith("cq4")).length); // write data into the inQueue msg.msg("somedata-2"); assertEquals(3, inQueueTmpDir.listFiles(file -> file.getName().endsWith("cq4")).length); // advance 2 cycles - we will end up with a missing file tp.advanceMillis(2000); msg.msg("somedata-3"); msg.msg("somedata-4"); assertEquals("Should be a missing cycle file", 4, inQueueTmpDir.listFiles(file -> file.getName().endsWith("cq4")).length); AtomicReference<String> actualValue = new AtomicReference<>(); // check that we are able to pick up from where we left off, in other words the next read should be somedata-2 { ExcerptTailer excerptTailer = inQueue.createTailer().afterLastWritten(outQueue); MethodReader methodReader = excerptTailer.methodReader((Msg) actualValue::set); methodReader.readOne(); assertEquals("somedata-2", actualValue.get()); methodReader.readOne(); assertEquals("somedata-3", actualValue.get()); methodReader.readOne(); assertEquals("somedata-4", actualValue.get()); assertFalse(methodReader.readOne()); } } } } finally { executorService.shutdown(); executorService.awaitTermination(1, TimeUnit.SECONDS); executorService.shutdownNow(); } } @Test public void shouldAllowDirectoryToBeDeletedWhenQueueIsClosed() throws IOException { if (OS.isWindows()) { System.err.println("#460 Cannot test deleting after close on windows"); return; } final File dir = DirectoryUtils.tempDir("to-be-deleted"); try (final ChronicleQueue queue = builder(dir, wireType). testBlockSize().build()) { try (final DocumentContext dc = queue.acquireAppender().writingDocument()) { dc.wire().write().text("foo"); } try (final DocumentContext dc = queue.createTailer().readingDocument()) { assertEquals("foo", dc.wire().read().text()); } } Files.walk(dir.toPath()).forEach(p -> { if (!Files.isDirectory(p)) { assertTrue(p.toString(), p.toFile().delete()); } }); assertTrue(dir.delete()); } @Test public void testReadingLessBytesThanWritten() { try (final ChronicleQueue queue = builder(getTmpDir(), wireType) .build()) { final ExcerptAppender appender = queue.acquireAppender(); final Bytes<byte[]> expected = Bytes.wrapForRead("some long message".getBytes(ISO_8859_1)); for (int i = 0; i < 10; i++) { appender.writeBytes(expected); } final ExcerptTailer tailer = queue.createTailer(); // Sequential read for (int i = 0; i < 10; i++) { Bytes b = Bytes.allocateDirect(8); tailer.readBytes(b); assertEquals(expected.readInt(0), b.readInt(0)); b.releaseLast(); } } } @Test public void testAppendAndRead() { try (final ChronicleQueue queue = builder(getTmpDir(), this.wireType) .build()) { final ExcerptAppender appender = queue.acquireAppender(); final int cycle = appender.cycle(); for (int i = 0; i < 10; i++) { final int n = i; appender.writeDocument(w -> w.write(TestKey.test).int32(n)); assertEquals(n, queue.rollCycle().toSequenceNumber(appender.lastIndexAppended())); } final ExcerptTailer tailer = queue.createTailer(); // Sequential read for (int i = 0; i < 10; i++) { final int n = i; assertTrue(tailer.readDocument(r -> assertEquals(n, r.read(TestKey.test).int32()))); assertEquals(n + 1, queue.rollCycle().toSequenceNumber(tailer.index())); } // Random read for (int i = 0; i < 10; i++) { final int n = i; assertTrue("n: " + n, tailer.moveToIndex(queue.rollCycle().toIndex(cycle, n))); assertTrue("n: " + n, tailer.readDocument(r -> assertEquals(n, r.read(TestKey.test).int32()))); assertEquals(n + 1, queue.rollCycle().toSequenceNumber(tailer.index())); } } } @Test public void testReadAndAppend() throws InterruptedException { try (final ChronicleQueue queue = builder(getTmpDir(), this.wireType).build()) { final CountDownLatch started = new CountDownLatch(1); int[] results = new int[2]; Thread t = new Thread(() -> { try { started.countDown(); final ExcerptTailer tailer = queue.createTailer(); for (int i = 0; i < 2; ) { boolean read = tailer.readDocument(r -> { int result = r.read(TestKey.test).int32(); results[result] = result; }); if (read) { i++; } else { // Pause for a little Jvm.pause(10); } } } catch (Exception e) { e.printStackTrace(); fail("exception"); } }); t.setDaemon(true); t.start(); assertTrue(started.await(1, TimeUnit.SECONDS)); final ExcerptAppender appender = queue.acquireAppender(); for (int i = 0; i < 2; i++) { final int n = i; appender.writeDocument(w -> w.write(TestKey.test).int32(n)); } t.join(1_000); assertArrayEquals(new int[]{0, 1}, results); } } @Test public void testCheckIndexWithWritingDocument() { doTestCheckIndex( (appender, n) -> { try (final DocumentContext dc = appender.writingDocument()) { dc.wire().writeEventName("").object("" + n); } }); } @Test public void testCheckIndexWithWritingDocument2() { doTestCheckIndex( (appender, n) -> { try (final DocumentContext dc = appender.writingDocument()) { dc.wire().bytes().writeUtf8("Hello") .writeStopBit(12345) .writeStopBit(1.2) // float also supported. .writeInt(1); } }); } @Test public void testCheckIndexWithWriteBytes() { doTestCheckIndex( (appender, n) -> appender.writeBytes(Bytes.from("Message-" + n))); } @Test public void testCheckIndexWithWriteBytes2() { doTestCheckIndex( (appender, n) -> appender.writeBytes(b -> b.append8bit("Message-").append(n))); } @Test public void testCheckIndexWithWriteBytes3() { doTestCheckIndex( (appender, n) -> appender.writeBytes(b -> b.writeUtf8("Hello") .writeStopBit(12345) .writeStopBit(1.2) // float also supported. .writeInt(1))); } @Test public void testCheckIndexWithWriteMap() { doTestCheckIndex( (appender, n) -> appender.writeMap(new HashMap<String, String>() {{ put("key", "Message-" + n); }})); } @Test public void testCheckIndexWithWriteText() { doTestCheckIndex( (appender, n) -> appender.writeText("Message-" + n) ); } void doTestCheckIndex(@NotNull BiConsumer<ExcerptAppender, Integer> writeTo) { SetTimeProvider stp = new SetTimeProvider(); stp.currentTimeMillis(System.currentTimeMillis() - 3 * 86400_000L); try (final ChronicleQueue queue = builder(getTmpDir(), wireType) .timeProvider(stp) .build()) { final ExcerptAppender appender = queue.acquireAppender(); ExcerptTailer tailer = queue.createTailer(); int cycle = appender.cycle(); for (int i = 0; i <= 5; i++) { final int n = i; writeTo.accept(appender, n); assertEquals(cycle + i, appender.cycle()); try (DocumentContext dc = tailer.readingDocument()) { long index = tailer.index(); assertEquals(appender.cycle(), tailer.cycle()); assertEquals(cycle + i, DAILY.toCycle(index)); } stp.currentTimeMillis(stp.currentTimeMillis() + 86400_000L); } } } @Test public void testAppendAndReadWithRollingB() { SetTimeProvider stp = new SetTimeProvider(); stp.currentTimeMillis(System.currentTimeMillis() - 3 * 86400_000L); try (final ChronicleQueue queue = builder(getTmpDir(), this.wireType) .rollCycle(TEST_DAILY) .timeProvider(stp) .build()) { final ExcerptAppender appender = queue.acquireAppender(); appender.writeDocument(w -> w.write(TestKey.test).int32(0)); appender.writeDocument(w -> w.write(TestKey.test2).int32(1000)); int cycle = appender.cycle(); for (int i = 1; i <= 5; i++) { stp.currentTimeMillis(stp.currentTimeMillis() + 86400_000L); final int n = i; appender.writeDocument(w -> w.write(TestKey.test).int32(n)); assertEquals(cycle + i, appender.cycle()); appender.writeDocument(w -> w.write(TestKey.test2).int32(n + 1000)); assertEquals(cycle + i, appender.cycle()); } /* Note this means the file has rolled --- !!not-ready-meta-data! #binary ... */ assumeFalse(encryption); assumeFalse(wireType == WireType.DEFAULT_ZERO_BINARY); final ExcerptTailer tailer = queue.createTailer().toStart(); for (int i = 0; i < 6; i++) { final int n = i; boolean condition = tailer.readDocument(r -> assertEquals(n, r.read(TestKey.test).int32())); assertTrue("i : " + i, condition); assertEquals(cycle + i, tailer.cycle()); boolean condition2 = tailer.readDocument(r -> assertEquals(n + 1000, r.read(TestKey.test2).int32())); assertTrue("i2 : " + i, condition2); assertEquals(cycle + i, tailer.cycle()); } } } @Test public void testAppendAndReadAtIndex() { try (final ChronicleQueue queue = builder(getTmpDir(), this.wireType) .rollCycle(TEST2_DAILY) .build()) { final ExcerptAppender appender = queue.acquireAppender(); appender.cycle(); for (int i = 0; i < 5; i++) { final int n = i; appender.writeDocument(w -> w.write(TestKey.test).int32(n)); assertEquals(i, queue.rollCycle().toSequenceNumber(appender.lastIndexAppended())); } final ExcerptTailer tailer = queue.createTailer(); for (int i = 0; i < 5; i++) { final long index = queue.rollCycle().toIndex(appender.cycle(), i); assertTrue(tailer.moveToIndex(index)); final int n = i; assertTrue(tailer.readDocument(r -> assertEquals(n, queue.rollCycle().toSequenceNumber(r.read(TestKey.test) .int32())))); long index2 = tailer.index(); long sequenceNumber = queue.rollCycle().toSequenceNumber(index2); assertEquals(n + 1, sequenceNumber); } } } @Test public void testSimpleWire() { try (final ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .build()) { final ExcerptAppender appender = chronicle.acquireAppender(); appender.writeDocument(wire -> wire.write(() -> "FirstName").text("Steve")); appender.writeDocument(wire -> wire.write(() -> "Surname").text("Jobs")); StringBuilder first = new StringBuilder(); StringBuilder surname = new StringBuilder(); final ExcerptTailer tailer = chronicle.createTailer(); tailer.readDocument(wire -> wire.read(() -> "FirstName").text(first)); tailer.readDocument(wire -> wire.read(() -> "Surname").text(surname)); assertEquals("Steve Jobs", first + " " + surname); } } @Test public void testIndexWritingDocument() { try (final ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .build()) { final ExcerptAppender appender = chronicle.acquireAppender(); long index; try (DocumentContext dc = appender.writingDocument()) { dc.wire().write(() -> "FirstName").text("Quartilla"); index = dc.index(); } try (DocumentContext dc = appender.writingDocument(true)) { dc.wire().write(() -> "FirstName").text("Quartilla"); } assertEquals(index, appender.lastIndexAppended()); } } @Test public void testReadingWritingMarshallableDocument() { try (final ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .build()) { MyMarshable myMarshable = new MyMarshable(); final ExcerptAppender appender = chronicle.acquireAppender(); try (DocumentContext dc = appender.writingDocument()) { dc.wire().write("myMarshable").typedMarshallable(myMarshable); } ExcerptTailer tailer = chronicle.createTailer(); try (DocumentContext dc = tailer.readingDocument()) { assertEquals(myMarshable, dc.wire().read(() -> "myMarshable").typedMarshallable()); } } } @Test public void testMetaData() { try (final ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .build()) { final ExcerptAppender appender = chronicle.acquireAppender(); try (DocumentContext dc = appender.writingDocument(true)) { dc.wire().write(() -> "FirstName").text("Quartilla"); } try (DocumentContext dc = appender.writingDocument()) { dc.wire().write(() -> "FirstName").text("Rob"); } try (DocumentContext dc = appender.writingDocument(true)) { dc.wire().write(() -> "FirstName").text("Steve"); } final ExcerptTailer tailer = chronicle.createTailer(); StringBuilder event = new StringBuilder(); while (true) { try (DocumentContext dc = tailer.readingDocument(true)) { assertTrue(dc.isMetaData()); ValueIn in = dc.wire().read(event); // first we will pick up header, index etc. if (!StringUtils.isEqual(event, "FirstName")) continue; in.text("Quartilla", Assert::assertEquals); break; } } long robIndex; try (DocumentContext dc = tailer.readingDocument(true)) { assertTrue(dc.isData()); robIndex = dc.index(); dc.wire().read(() -> "FirstName").text("Rob", Assert::assertEquals); } while (true) { try (DocumentContext dc = tailer.readingDocument(true)) { assertTrue(dc.isMetaData()); ValueIn in = dc.wire().read(event); if (!StringUtils.isEqual(event, "FirstName")) continue; in.text("Steve", Assert::assertEquals); break; } } assertTrue(tailer.moveToIndex(robIndex)); try (DocumentContext dc = tailer.readingDocument(true)) { assertTrue(dc.isData()); dc.wire().read(() -> "FirstName").text("Rob", Assert::assertEquals); } } } @Test public void testReadingSecondDocumentNotExist() { try (final ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .build()) { final ExcerptAppender appender = chronicle.acquireAppender(); try (DocumentContext dc = appender.writingDocument()) { dc.wire().write(() -> "FirstName").text("Quartilla"); } final ExcerptTailer tailer = chronicle.createTailer(); try (DocumentContext dc = tailer.readingDocument()) { String text = dc.wire().read(() -> "FirstName").text(); assertEquals("Quartilla", text); } try (DocumentContext dc = tailer.readingDocument()) { assertFalse(dc.isPresent()); } } } @Test public void testDocumentIndexTest() { try (final ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .build()) { final ExcerptAppender appender = chronicle.acquireAppender(); try (DocumentContext dc = appender.writingDocument()) { long index = dc.index(); assertEquals(0, chronicle.rollCycle().toSequenceNumber(index)); dc.wire().write(() -> "FirstName").text("Quartilla"); } try (DocumentContext dc = appender.writingDocument()) { assertEquals(1, chronicle.rollCycle().toSequenceNumber(dc.index())); dc.wire().write(() -> "FirstName").text("Rob"); } try (DocumentContext dc = appender.writingDocument()) { assertEquals(2, chronicle.rollCycle().toSequenceNumber(dc.index())); dc.wire().write(() -> "FirstName").text("Rob"); } ExcerptTailer tailer = chronicle.createTailer(); try (DocumentContext dc = tailer.readingDocument()) { long index = dc.index(); assertEquals(0, chronicle.rollCycle().toSequenceNumber(index)); } try (DocumentContext dc = tailer.readingDocument()) { assertEquals(1, chronicle.rollCycle().toSequenceNumber(dc.index())); } try (DocumentContext dc = tailer.readingDocument()) { assertEquals(2, chronicle.rollCycle().toSequenceNumber(dc.index())); } } } @Test public void testReadingSecondDocumentNotExistIncludingMeta() { try (final ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .build()) { final ExcerptAppender appender = chronicle.acquireAppender(); try (DocumentContext dc = appender.writingDocument()) { dc.wire().write(() -> "FirstName").text("Quartilla"); } final ExcerptTailer tailer = chronicle.createTailer(); StringBuilder event = new StringBuilder(); while (true) { try (DocumentContext dc = tailer.readingDocument(true)) { ValueIn in = dc.wire().read(event); if (!StringUtils.isEqual(event, "FirstName")) continue; in.text("Quartilla", Assert::assertEquals); break; } } try (DocumentContext dc = tailer.readingDocument()) { assertFalse(dc.isPresent()); } } } @Test public void testSimpleByteTest() { try (final ChronicleQueue chronicle = builder(getTmpDir(), wireType) .rollCycle(TEST2_DAILY) .build()) { final ExcerptAppender appender = chronicle.acquireAppender(); Bytes steve = Bytes.allocateDirect("Steve".getBytes()); appender.writeBytes(steve); Bytes jobs = Bytes.allocateDirect("Jobs".getBytes()); appender.writeBytes(jobs); final ExcerptTailer tailer = chronicle.createTailer(); Bytes bytes = Bytes.elasticByteBuffer(); try { tailer.readBytes(bytes); assertEquals("Steve", bytes.toString()); bytes.clear(); tailer.readBytes(bytes); assertEquals("Jobs", bytes.toString()); } finally { steve.releaseLast(); jobs.releaseLast(); bytes.releaseLast(); } } } @Test public void testReadAtIndex() { try (final RollingChronicleQueue queue = builder(getTmpDir(), wireType) .indexCount(8) .indexSpacing(8) .build()) { final ExcerptAppender appender = queue.acquireAppender(); // create 100 documents for (int i = 0; i < 100; i++) { final int j = i; try (final DocumentContext context = appender.writingDocument()) { context.wire().write(() -> "key").text("value=" + j); } } long lastIndex = appender.lastIndexAppended(); final int cycle = queue.rollCycle().toCycle(lastIndex); assertEquals(queue.firstCycle(), cycle); assertEquals(queue.lastCycle(), cycle); final ExcerptTailer tailer = queue.createTailer(); StringBuilder sb = new StringBuilder(); for (int i : new int[]{0, 8, 7, 9, 64, 65, 66}) { final long index = queue.rollCycle().toIndex(cycle, i); assertTrue("i: " + i, tailer.moveToIndex( index)); final DocumentContext context = tailer.readingDocument(); assertEquals(index, context.index()); context.wire().read(() -> "key").text(sb); assertEquals("value=" + i, sb.toString()); } } } @Ignore("long running test") @Test public void testReadAtIndex4MB() { try (final ChronicleQueue queue = SingleChronicleQueueBuilder.builder(getTmpDir(), this.wireType).rollCycle(SMALL_DAILY) .build()) { final ExcerptAppender appender = queue.acquireAppender(); System.out.print("Percent written="); for (long i = 0; i < TIMES; i++) { final long j = i; appender.writeDocument(wire -> wire.write(() -> "key").text("value=" + j)); if (i % (TIMES / 20) == 0) { System.out.println("" + (i * 100 / TIMES) + "%, "); } } long lastIndex = appender.lastIndexAppended(); final int cycle = queue.rollCycle().toCycle(lastIndex); final ExcerptTailer tailer = queue.createTailer(); // QueueDumpMain.dump(file, new PrintWriter(System.out)); StringBuilder sb = new StringBuilder(); for (long i = 0; i < (4L << 20L); i++) { assertTrue(tailer.moveToIndex(queue.rollCycle().toIndex(cycle, i))); tailer.readDocument(wire -> wire.read(() -> "key").text(sb)); assertEquals("value=" + i, sb.toString()); if (i % (TIMES / 20) == 0) { System.out.println("Percent read= " + (i * 100 / TIMES) + "%"); } } } } @Test public void testLastWrittenIndexPerAppender() { try (final ChronicleQueue queue = builder(getTmpDir(), this.wireType) .build()) { final ExcerptAppender appender = queue.acquireAppender(); appender.writeDocument(wire -> wire.write(() -> "key").text("test")); assertEquals(0, queue.rollCycle().toSequenceNumber(appender.lastIndexAppended())); } } @Test(expected = IllegalStateException.class) public void testLastWrittenIndexPerAppenderNoData() { try (final ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .build()) { final ExcerptAppender appender = chronicle.acquireAppender(); appender.lastIndexAppended(); fail(); } } @Test(expected = IllegalStateException.class) //: no messages written public void testNoMessagesWritten() { try (final ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .build()) { final ExcerptAppender appender = chronicle.acquireAppender(); appender.lastIndexAppended(); } } @Test public void testHeaderIndexReadAtIndex() { try (final ChronicleQueue queue = builder(getTmpDir(), this.wireType) .build()) { final ExcerptAppender appender = queue.acquireAppender(); final int cycle = appender.cycle(); // create 100 documents for (int i = 0; i < 100; i++) { final int j = i; appender.writeDocument(wire -> wire.write(() -> "key").text("value=" + j)); } final ExcerptTailer tailer = queue.createTailer(); assertTrue(tailer.moveToIndex(queue.rollCycle().toIndex(cycle, 0))); StringBuilder sb = new StringBuilder(); tailer.readDocument(wire -> wire.read(() -> "key").text(sb)); assertEquals("value=0", sb.toString()); } } /** * test that if we make EPOC the current time, then the cycle is == 0 * * @ */ @Test public void testEPOC() { try (final ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .epoch(System.currentTimeMillis()) .rollCycle(RollCycles.HOURLY) .build()) { final ExcerptAppender appender = chronicle.acquireAppender(); appender.writeDocument(wire -> wire.write(() -> "key").text("value=v")); assertEquals(0, appender.cycle()); } } @Test public void shouldBeAbleToReadFromQueueWithNonZeroEpoch() { try (final ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .epoch(System.currentTimeMillis()) .rollCycle(RollCycles.DAILY) .build()) { final ExcerptAppender appender = chronicle.acquireAppender(); appender.writeDocument(wire -> wire.write(() -> "key").text("value=v")); assertEquals(0, appender.cycle()); final ExcerptTailer excerptTailer = chronicle.createTailer().toStart(); assertTrue(excerptTailer.readingDocument().isPresent()); } } @Test public void shouldHandleLargeEpoch() { try (final ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .epoch(System.currentTimeMillis()) .epoch(1284739200000L) .rollCycle(DAILY) .build()) { final ExcerptAppender appender = chronicle.acquireAppender(); appender.writeDocument(wire -> wire.write(() -> "key").text("value=v")); final ExcerptTailer excerptTailer = chronicle.createTailer().toStart(); assertTrue(excerptTailer.readingDocument().isPresent()); } } @Test public void testNegativeEPOC() { for (int h = -14; h <= 14; h++) { try (final ChronicleQueue chronicle = builder(getTmpDir(), wireType) .epoch(TimeUnit.HOURS.toMillis(h)) .build()) { final ExcerptAppender appender = chronicle.acquireAppender(); appender.writeDocument(wire -> wire.write(() -> "key").text("value=v")); chronicle.createTailer() .readDocument(wire -> { assertEquals("value=v", wire.read("key").text()); }); } } } @Test public void testIndex() { try (final ChronicleQueue queue = builder(getTmpDir(), this.wireType) .rollCycle(RollCycles.HOURLY) .build()) { final ExcerptAppender appender = queue.acquireAppender(); int cycle = appender.cycle(); // create 100 documents for (int i = 0; i < 5; i++) { final int j = i; appender.writeDocument(wire -> wire.write(() -> "key").text("value=" + j)); if (i == 2) { final long cycle1 = queue.rollCycle().toCycle(appender.lastIndexAppended()); assertEquals(cycle1, cycle); } } final ExcerptTailer tailer = queue.createTailer(); assertTrue(tailer.moveToIndex(queue.rollCycle().toIndex(cycle, 2))); StringBuilder sb = new StringBuilder(); tailer.readDocument(wire -> wire.read(() -> "key").text(sb)); assertEquals("value=2", sb.toString()); tailer.readDocument(wire -> wire.read(() -> "key").text(sb)); assertEquals("value=3", sb.toString()); tailer.readDocument(wire -> wire.read(() -> "key").text(sb)); assertEquals("value=4", sb.toString()); } } @Test public void testReadingDocument() { try (final ChronicleQueue queue = builder(getTmpDir(), this.wireType) .rollCycle(RollCycles.HOURLY) .build()) { final ExcerptAppender appender = queue.acquireAppender(); long cycle = appender.cycle(); // create 100 documents for (int i = 0; i < 5; i++) { final int j = i; appender.writeDocument(wire -> wire.write(() -> "key").text("value=" + j)); if (i == 2) { final long cycle1 = queue.rollCycle().toCycle(appender.lastIndexAppended()); assertEquals(cycle1, cycle); } } final ExcerptTailer tailer = queue.createTailer(); final StringBuilder sb = Wires.acquireStringBuilder(); try (final DocumentContext dc = tailer.readingDocument()) { assert dc.isPresent(); assert dc.isData(); dc.wire().read(() -> "key").text(sb); assertEquals("value=0", sb.toString()); } try (final DocumentContext dc = tailer.readingDocument()) { assert dc.isPresent(); assert dc.isData(); dc.wire().read(() -> "key").text(sb); assertEquals("value=1", sb.toString()); } try (final DocumentContext dc = tailer.readingDocument()) { assert dc.isPresent(); assert dc.isData(); dc.wire().read(() -> "key").text(sb); assertEquals("value=2", sb.toString()); } try (final DocumentContext dc = tailer.readingDocument()) { assert dc.isPresent(); assert dc.isData(); dc.wire().read(() -> "key").text(sb); assertEquals("value=3", sb.toString()); } try (final DocumentContext dc = tailer.readingDocument()) { assert dc.isPresent(); assert dc.isData(); dc.wire().read(() -> "key").text(sb); assertEquals("value=4", sb.toString()); } try (final DocumentContext dc = tailer.readingDocument()) { assert !dc.isPresent(); assert !dc.isData(); assert !dc.isMetaData(); } } } @Test public void testReadingDocumentWithFirstAMove() { try (final ChronicleQueue queue = builder(getTmpDir(), this.wireType) .rollCycle(RollCycles.HOURLY) .build()) { final ExcerptAppender appender = queue.acquireAppender(); int cycle = appender.cycle(); // create 100 documents for (int i = 0; i < 5; i++) { final int j = i; appender.writeDocument(wire -> wire.write(() -> "key").text("value=" + j)); if (i == 2) { final long cycle1 = queue.rollCycle().toCycle(appender.lastIndexAppended()); assertEquals(cycle1, cycle); } } final ExcerptTailer tailer = queue.createTailer(); assertTrue(tailer.moveToIndex(queue.rollCycle().toIndex(cycle, 2))); final StringBuilder sb = Wires.acquireStringBuilder(); try (final DocumentContext dc = tailer.readingDocument()) { assert dc.isPresent(); assert dc.isData(); dc.wire().read(() -> "key").text(sb); assertEquals("value=2", sb.toString()); } try (final DocumentContext dc = tailer.readingDocument()) { assert dc.isPresent(); assert dc.isData(); dc.wire().read(() -> "key").text(sb); assertEquals("value=3", sb.toString()); } try (final DocumentContext dc = tailer.readingDocument()) { assert dc.isPresent(); assert dc.isData(); dc.wire().read(() -> "key").text(sb); assertEquals("value=4", sb.toString()); } try (final DocumentContext dc = tailer.readingDocument()) { assert !dc.isPresent(); assert !dc.isData(); assert !dc.isMetaData(); } } } @Test public void testReadingDocumentWithFirstAMoveWithEpoch() { Instant hourly = Instant.parse("2018-02-12T00:59:59.999Z"); Instant minutely = Instant.parse("2018-02-12T00:00:59.999Z"); Date epochHourlyFirstCycle = Date.from(hourly); Date epochMinutelyFirstCycle = Date.from(minutely); Date epochHourlySecondCycle = Date.from(hourly.plusMillis(1)); Date epochMinutelySecondCycle = Date.from(minutely.plusMillis(1)); doTestEpochMove(epochHourlyFirstCycle.getTime(), RollCycles.MINUTELY); doTestEpochMove(epochHourlySecondCycle.getTime(), RollCycles.MINUTELY); doTestEpochMove(epochHourlyFirstCycle.getTime(), RollCycles.HOURLY); doTestEpochMove(epochHourlySecondCycle.getTime(), RollCycles.HOURLY); doTestEpochMove(epochHourlyFirstCycle.getTime(), RollCycles.DAILY); doTestEpochMove(epochHourlySecondCycle.getTime(), RollCycles.DAILY); doTestEpochMove(epochMinutelyFirstCycle.getTime(), RollCycles.MINUTELY); doTestEpochMove(epochMinutelySecondCycle.getTime(), RollCycles.MINUTELY); doTestEpochMove(epochMinutelyFirstCycle.getTime(), RollCycles.HOURLY); doTestEpochMove(epochMinutelySecondCycle.getTime(), RollCycles.HOURLY); doTestEpochMove(epochMinutelyFirstCycle.getTime(), RollCycles.DAILY); doTestEpochMove(epochMinutelySecondCycle.getTime(), RollCycles.DAILY); } private void doTestEpochMove(long epoch, RollCycle rollCycle) { try (final ChronicleQueue queue = builder(getTmpDir(), this.wireType) .rollCycle(rollCycle) .epoch(epoch) .build()) { final ExcerptAppender appender = queue.acquireAppender(); int cycle = appender.cycle(); // create 100 documents int last = 4; for (int i = 0; i <= last; i++) { final int j = i; appender.writeDocument(wire -> wire.write(() -> "key").text("value=" + j)); if (i == last) { final long cycle1 = queue.rollCycle().toCycle(appender.lastIndexAppended()); if (cycle + 1 != cycle1) assertEquals(cycle, cycle1); } } final ExcerptTailer tailer = queue.createTailer(); assertTrue(tailer.moveToIndex(queue.rollCycle().toIndex(cycle, 2))); final StringBuilder sb = Wires.acquireStringBuilder(); try (final DocumentContext dc = tailer.readingDocument()) { assert dc.isPresent(); assert dc.isData(); dc.wire().read(() -> "key").text(sb); assertEquals("value=2", sb.toString()); } try (final DocumentContext dc = tailer.readingDocument()) { assert dc.isPresent(); assert dc.isData(); dc.wire().read(() -> "key").text(sb); assertEquals("value=3", sb.toString()); } try (final DocumentContext dc = tailer.readingDocument()) { assert dc.isPresent(); assert dc.isData(); dc.wire().read(() -> "key").text(sb); assertEquals("value=4", sb.toString()); } try (final DocumentContext dc = tailer.readingDocument()) { assert !dc.isPresent(); assert !dc.isData(); assert !dc.isMetaData(); } } } @Test public void testAppendedBeforeToEnd() { File dir = getTmpDir(); try (ChronicleQueue chronicle = builder(dir, this.wireType) .rollCycle(RollCycles.TEST_SECONDLY) .build(); ChronicleQueue chronicle2 = builder(dir, this.wireType) .rollCycle(RollCycles.TEST_SECONDLY) .build()) { ExcerptTailer tailer = chronicle.createTailer(); ExcerptAppender append = chronicle2.acquireAppender(); append.writeDocument(w -> w.write(() -> "test").text("text")); while (tailer.state() == TailerState.UNINITIALISED) tailer.toEnd(); try (DocumentContext dc = tailer.readingDocument()) { assertFalse(tailer.index() + " " + tailer.state(), dc.isPresent()); } append.writeDocument(w -> w.write(() -> "test").text("text2")); try (DocumentContext dc = tailer.readingDocument()) { assertTrue(dc.isPresent()); assertEquals("text2", dc.wire().read("test").text()); } } } @Test(expected = AssertionError.class) public void testReentrant() { boolean assertsEnabled = false; //noinspection ConstantConditions assert assertsEnabled = true; //noinspection ConstantConditions assumeTrue(assertsEnabled); File tmpDir = DirectoryUtils.tempDir("testReentrant"); try (final ChronicleQueue queue = binary(tmpDir) .testBlockSize() .rollCycle(RollCycles.TEST_DAILY) .build()) { ExcerptAppender appender = queue.acquireAppender(); try (DocumentContext dc = appender.writingDocument()) { dc.wire().write("some").text("data"); try (DocumentContext dc2 = appender.writingDocument()) { dc2.wire().write("some2").text("other"); } } } } @Test public void testToEnd() throws InterruptedException { File dir = getTmpDir(); try (ChronicleQueue queue = builder(dir, wireType) .rollCycle(RollCycles.HOURLY) .build()) { ExcerptTailer tailer = queue.createTailer(); // move to the end even though it doesn't exist yet. tailer.toEnd(); try (ChronicleQueue chronicle2 = builder(dir, wireType) .rollCycle(RollCycles.HOURLY) .build()) { ExcerptAppender append = chronicle2.acquireAppender(); append.writeDocument(w -> w.write(() -> "test").text("text")); } // this is needed to avoid caching of first and last cycle, see SingleChronicleQueue#setFirstAndLastCycle Thread.sleep(1); try (DocumentContext dc = tailer.readingDocument()) { try (final SingleChronicleQueue build = builder(dir, wireType).rollCycle(HOURLY).build()) { String message = "dump: " + build.dump(); assertTrue(message, dc.isPresent()); assertEquals(message, "text", dc.wire().read("test").text()); } } } } @Test public void testToEnd2() { File dir = getTmpDir(); try (ChronicleQueue chronicle = builder(dir, wireType) .build(); ChronicleQueue chronicle2 = builder(dir, wireType) .build()) { ExcerptAppender append = chronicle2.acquireAppender(); append.writeDocument(w -> w.write(() -> "test").text("before text")); ExcerptTailer tailer = chronicle.createTailer(); // move to the end even though it doesn't exist yet. tailer.toEnd(); append.writeDocument(w -> w.write(() -> "test").text("text")); assertTrue(tailer.readDocument(w -> w.read(() -> "test").text("text", Assert::assertEquals))); } } @Test public void testToEndOnDeletedQueueFiles() throws IOException { if (OS.isWindows()) { System.err.println("#460 Cannot test delete after close on windows"); return; } File dir = getTmpDir(); try (ChronicleQueue q = builder(dir, wireType).build()) { ExcerptAppender append = q.acquireAppender(); append.writeDocument(w -> w.write(() -> "test").text("before text")); ExcerptTailer tailer = q.createTailer(); // move to the end even though it doesn't exist yet. tailer.toEnd(); append.writeDocument(w -> w.write(() -> "test").text("text")); assertTrue(tailer.readDocument(w -> w.read(() -> "test").text("text", Assert::assertEquals))); Files.find(dir.toPath(), 1, (p, basicFileAttributes) -> p.toString().endsWith("cq4"), FileVisitOption.FOLLOW_LINKS) .forEach(path -> assertTrue(path.toFile().delete())); try (ChronicleQueue q2 = builder(dir, wireType).build()) { tailer = q2.createTailer(); tailer.toEnd(); assertEquals(TailerState.UNINITIALISED, tailer.state()); append = q2.acquireAppender(); append.writeDocument(w -> w.write(() -> "test").text("before text")); assertTrue(tailer.readDocument(w -> w.read(() -> "test").text("before text", Assert::assertEquals))); } } } @Test public void testReadWrite() { File dir = getTmpDir(); try (ChronicleQueue chronicle = builder(dir, wireType) .rollCycle(RollCycles.HOURLY) .testBlockSize() .build(); ChronicleQueue chronicle2 = builder(dir, wireType) .rollCycle(RollCycles.HOURLY) .testBlockSize() .build()) { ExcerptAppender append = chronicle2.acquireAppender(); int runs = 50_000; for (int i = 0; i < runs; i++) { append.writeDocument(w -> w .write(() -> "test - message") .text("text")); } ExcerptTailer tailer = chronicle.createTailer(); ExcerptTailer tailer2 = chronicle.createTailer(); ExcerptTailer tailer3 = chronicle.createTailer(); ExcerptTailer tailer4 = chronicle.createTailer(); for (int i = 0; i < runs; i++) { if (i % 10000 == 0) System.gc(); if (i % 2 == 0) assertTrue(tailer2.readDocument(w -> w.read(() -> "test - message").text("text", Assert::assertEquals))); if (i % 3 == 0) assertTrue(tailer3.readDocument(w -> w.read(() -> "test - message").text("text", Assert::assertEquals))); if (i % 4 == 0) assertTrue(tailer4.readDocument(w -> w.read(() -> "test - message").text("text", Assert::assertEquals))); assertTrue(tailer.readDocument(w -> w.read(() -> "test - message").text("text", Assert::assertEquals))); } } } @Test public void testReadingDocumentForEmptyQueue() { File dir = getTmpDir(); try (ChronicleQueue chronicle = builder(dir, this.wireType) .rollCycle(RollCycles.HOURLY) .build()) { ExcerptTailer tailer = chronicle.createTailer(); // DocumentContext is empty as we have no queue and don't know what the wire type will be. try (DocumentContext dc = tailer.readingDocument()) { assertFalse(dc.isPresent()); } try (ChronicleQueue chronicle2 = builder(dir, this.wireType) .rollCycle(RollCycles.HOURLY) .build()) { ExcerptAppender appender = chronicle2.acquireAppender(); appender.writeDocument(w -> w.write(() -> "test - message").text("text")); while (tailer.state() == TailerState.UNINITIALISED) tailer.toStart(); // DocumentContext should not be empty as we know what the wire type will be. try (DocumentContext dc = tailer.readingDocument()) { assertTrue(dc.isPresent()); dc.wire().read(() -> "test - message").text("text", Assert::assertEquals); } } } } @Test public void testMetaData6() { try (final ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .rollCycle(TEST2_DAILY) .build()) { final ExcerptAppender appender = chronicle.acquireAppender(); try (DocumentContext dc = appender.writingDocument(true)) { dc.wire().write(() -> "FirstName").text("Quartilla"); } try (DocumentContext dc = appender.writingDocument()) { assertFalse(dc.isMetaData()); dc.wire().write(() -> "FirstName").text("Helen"); } try (DocumentContext dc = appender.writingDocument(true)) { dc.wire().write(() -> "FirstName").text("Steve"); } final ExcerptTailer tailer = chronicle.createTailer(); StringBuilder event = new StringBuilder(); while (true) { try (DocumentContext dc = tailer.readingDocument(true)) { assertTrue(dc.isMetaData()); ValueIn in = dc.wire().read(event); if (!StringUtils.isEqual(event, "FirstName")) continue; in.text("Quartilla", Assert::assertEquals); break; } } try (DocumentContext dc = tailer.readingDocument(true)) { assertTrue(dc.isData()); assertTrue(dc.isPresent()); dc.wire().read(() -> "FirstName").text("Helen", Assert::assertEquals); } while (true) { try (DocumentContext dc = tailer.readingDocument(true)) { assertTrue(dc.isMetaData()); ValueIn in = dc.wire().read(event); if (!StringUtils.isEqual(event, "FirstName")) continue; in.text("Steve", Assert::assertEquals); break; } } assertEquals(expectedMetaDataTest2(), chronicle.dump()); } } @NotNull protected String expectedMetaDataTest2() { if (wireType == WireType.BINARY || wireType == WireType.BINARY_LIGHT || wireType == WireType.COMPRESSED_BINARY) return "--- !!meta-data #binary\n" + "header: !SCQStore {\n" + " writePosition: [\n" + " 544,\n" + " 2336462209024\n" + " ],\n" + " indexing: !SCQSIndexing {\n" + " indexCount: 16,\n" + " indexSpacing: 2,\n" + " index2Index: 196,\n" + " lastIndex: 2\n" + " },\n" + " dataFormat: 1\n" + "}\n" + "# position: 196, header: -1\n" + "--- !!meta-data #binary\n" + "index2index: [\n" + " # length: 16, used: 1\n" + " 360,\n" + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n" + "]\n" + "# position: 360, header: -1\n" + "--- !!meta-data #binary\n" + "index: [\n" + " # length: 16, used: 1\n" + " 544,\n" + " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n" + "]\n" + "# position: 520, header: -1\n" + "--- !!meta-data #binary\n" + "FirstName: Quartilla\n" + "# position: 544, header: 0\n" + "--- !!data #binary\n" + "FirstName: Helen\n" + "# position: 564, header: 0\n" + "--- !!meta-data #binary\n" + "FirstName: Steve\n" + "...\n" + "# 130484 bytes remaining\n"; throw new IllegalStateException("unknown type " + wireType); } @Test(expected = IllegalArgumentException.class) public void dontPassQueueToReader() { try (ChronicleQueue queue = binary(getTmpDir()).build()) { queue.createTailer().afterLastWritten(queue).methodReader(); } } @Test public void testToEndBeforeWrite() { try (ChronicleQueue chronicle = builder(getTmpDir(), wireType) .rollCycle(TEST2_DAILY) .build(); ExcerptAppender appender = chronicle.acquireAppender(); ExcerptTailer tailer = chronicle.createTailer()) { int entries = chronicle.rollCycle().defaultIndexSpacing() * 2 + 2; for (int i = 0; i < entries; i++) { tailer.toEnd(); int finalI = i; appender.writeDocument(w -> w.writeEventName("hello").text("world" + finalI)); tailer.readDocument(w -> w.read().text("world" + finalI, Assert::assertEquals)); } } } @Test public void testSomeMessages() { try (ChronicleQueue chronicle = builder(getTmpDir(), wireType) .rollCycle(TEST2_DAILY) .build()) { ExcerptAppender appender = chronicle.acquireAppender(); ExcerptTailer tailer = chronicle.createTailer(); int entries = chronicle.rollCycle().defaultIndexSpacing() * 2 + 2; for (long i = 0; i < entries; i++) { long finalI = i; appender.writeDocument(w -> w.writeEventName("hello").int64(finalI)); long seq = chronicle.rollCycle().toSequenceNumber(appender.lastIndexAppended()); assertEquals(i, seq); // System.out.println(chronicle.dump()); tailer.readDocument(w -> w.read().int64(finalI, (a, b) -> assertEquals((long) a, b))); } } } @Test public void testForwardFollowedBackBackwardTailer() { try (ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .rollCycle(TEST2_DAILY) .build()) { ExcerptAppender appender = chronicle.acquireAppender(); int entries = chronicle.rollCycle().defaultIndexSpacing() + 2; for (int i = 0; i < entries; i++) { int finalI = i; appender.writeDocument(w -> w.writeEventName("hello").text("world" + finalI)); } for (int i = 0; i < 3; i++) { readForward(chronicle, entries); readBackward(chronicle, entries); } } } @Test public void shouldReadBackwardFromEndOfQueueWhenDirectionIsSetAfterMoveToEnd() { try (final ChronicleQueue queue = builder(getTmpDir(), this.wireType) .rollCycle(TEST2_DAILY) .build()) { final ExcerptAppender appender = queue.acquireAppender(); appender.writeDocument(w -> w.writeEventName("hello").text("world")); final ExcerptTailer tailer = queue.createTailer(); tailer.toEnd(); tailer.direction(TailerDirection.BACKWARD); assertTrue(tailer.readingDocument().isPresent()); } } void readForward(@NotNull ChronicleQueue chronicle, int entries) { try (ExcerptTailer forwardTailer = chronicle.createTailer() .direction(TailerDirection.FORWARD) .toStart()) { for (int i = 0; i < entries; i++) { try (DocumentContext documentContext = forwardTailer.readingDocument()) { assertTrue(documentContext.isPresent()); assertEquals(i, RollCycles.DAILY.toSequenceNumber(documentContext.index())); StringBuilder sb = Wires.acquireStringBuilder(); ValueIn valueIn = documentContext.wire().readEventName(sb); assertTrue("hello".contentEquals(sb)); String actual = valueIn.text(); assertEquals("world" + i, actual); } } try (DocumentContext documentContext = forwardTailer.readingDocument()) { assertFalse(documentContext.isPresent()); } } } void readBackward(@NotNull ChronicleQueue chronicle, int entries) { ExcerptTailer backwardTailer = chronicle.createTailer() .direction(TailerDirection.BACKWARD) .toEnd(); for (int i = entries - 1; i >= 0; i--) { try (DocumentContext documentContext = backwardTailer.readingDocument()) { assertTrue(documentContext.isPresent()); final long index = documentContext.index(); assertEquals("index: " + index, i, (int) index); assertEquals(i, RollCycles.DAILY.toSequenceNumber(index)); assertTrue(documentContext.isPresent()); StringBuilder sb = Wires.acquireStringBuilder(); ValueIn valueIn = documentContext.wire().readEventName(sb); assertTrue("hello".contentEquals(sb)); String actual = valueIn.text(); assertEquals("world" + i, actual); } } try (DocumentContext documentContext = backwardTailer.readingDocument()) { assertFalse(documentContext.isPresent()); } } @Test public void testOverreadForwardFromFutureCycleThenReadBackwardTailer() { RollCycle cycle = TEST2_DAILY; // when "forwardToFuture" flag is set, go one cycle to the future AtomicBoolean forwardToFuture = new AtomicBoolean(false); TimeProvider timeProvider = () -> forwardToFuture.get() ? System.currentTimeMillis() + TimeUnit.MILLISECONDS.toDays(1) : System.currentTimeMillis(); try (ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .rollCycle(cycle) .timeProvider(timeProvider) .build()) { ExcerptAppender appender = chronicle.acquireAppender(); appender.writeDocument(w -> w.writeEventName("hello").text("world")); // go to the cycle next to the one the write was made on forwardToFuture.set(true); ExcerptTailer forwardTailer = chronicle.createTailer() .direction(TailerDirection.FORWARD) .toStart(); try (DocumentContext context = forwardTailer.readingDocument()) { assertTrue(context.isPresent()); } try (DocumentContext context = forwardTailer.readingDocument()) { assertFalse(context.isPresent()); } ExcerptTailer backwardTailer = chronicle.createTailer() .direction(TailerDirection.BACKWARD) .toEnd(); try (DocumentContext context = backwardTailer.readingDocument()) { assertTrue(context.isPresent()); } } } @Test public void testLastIndexAppended() { try (ChronicleQueue chronicle = builder(getTmpDir(), this.wireType) .build()) { ExcerptAppender appender = chronicle.acquireAppender(); appender.writeDocument(w -> w.writeEventName("hello").text("world0")); final long nextIndexToWrite = appender.lastIndexAppended() + 1; appender.writeDocument(w -> w.getValueOut().bytes(new byte[0])); // System.out.println(chronicle.dump()); assertEquals(nextIndexToWrite, appender.lastIndexAppended()); } } @Test public void testZeroLengthMessage() { try (ChronicleQueue chronicle = builder(getTmpDir(), wireType) .rollCycle(TEST_DAILY) .build()) { ExcerptAppender appender = chronicle.acquireAppender(); appender.writeDocument(w -> { }); // System.out.println(chronicle.dump()); ExcerptTailer tailer = chronicle.createTailer(); try (DocumentContext dc = tailer.readingDocument()) { assertFalse(dc.wire().hasMore()); } } } @Test public void testMoveToWithAppender() { try (ChronicleQueue syncQ = builder(getTmpDir(), this.wireType) .build()) { InternalAppender sync = (InternalAppender) syncQ.acquireAppender(); File name2 = DirectoryUtils.tempDir(testName.getMethodName()); try (ChronicleQueue chronicle = builder(name2, this.wireType) .build()) { ExcerptAppender appender = chronicle.acquireAppender(); appender.writeDocument(w -> w.writeEventName("hello").text("world0")); appender.writeDocument(w -> w.writeEventName("hello").text("world1")); appender.writeDocument(w -> w.writeEventName("hello").text("world2")); ExcerptTailer tailer = chronicle.createTailer(); try (DocumentContext documentContext = tailer.readingDocument()) { sync.writeBytes(documentContext.index(), documentContext.wire().bytes()); } try (DocumentContext documentContext = tailer.readingDocument()) { String text = documentContext.wire().read().text(); assertEquals("world1", text); } } } } @Test public void testMapWrapper() { try (ChronicleQueue syncQ = builder(getTmpDir(), this.wireType) .build()) { File name2 = DirectoryUtils.tempDir(testName.getMethodName()); try (ChronicleQueue chronicle = builder(name2, this.wireType) .build()) { ExcerptAppender appender = chronicle.acquireAppender(); MapWrapper myMap = new MapWrapper(); myMap.map.put("hello", 1.2); appender.writeDocument(w -> w.write().object(myMap)); ExcerptTailer tailer = chronicle.createTailer(); try (DocumentContext documentContext = tailer.readingDocument()) { MapWrapper object = documentContext.wire().read().object(MapWrapper.class); assertEquals(1.2, object.map.get("hello"), 0.0); } } } } /** * if one appender if much further ahead than the other, then the new append should jump straight to the end rather than attempting to write a * positions that are already occupied */ @Test public void testAppendedSkipToEnd() { try (ChronicleQueue q = builder(getTmpDir(), this.wireType) .build()) { ExcerptAppender appender = q.acquireAppender(); ExcerptAppender appender2 = q.acquireAppender(); int indexCount = 100; for (int i = 0; i < indexCount; i++) { try (DocumentContext dc = appender.writingDocument()) { dc.wire().write("key").text("some more " + 1); assertEquals(i, q.rollCycle().toSequenceNumber(dc.index())); } } try (DocumentContext dc = appender2.writingDocument()) { dc.wire().write("key").text("some data " + indexCount); assertEquals(indexCount, q.rollCycle().toSequenceNumber(dc.index())); } } } @Test public void testAppendedSkipToEndMultiThreaded() throws InterruptedException { // some text to simulate load. StringBuilder sb = new StringBuilder(); for (int i = 0; i < 5; i++) sb.append(UUID.randomUUID()); String text = sb.toString(); try (ChronicleQueue q = builder(getTmpDir(), this.wireType) .rollCycle(TEST_SECONDLY) .build()) { System.err.println(q.file()); final ThreadLocal<ExcerptAppender> tl = ThreadLocal.withInitial(q::acquireAppender); int size = 50_000; int threadCount = 8; int sizePerThread = size / threadCount; CountDownLatch latch = new CountDownLatch(threadCount); for (int j = 0; j < threadCount; j++) { new Thread(() -> { for (int i = 0; i < sizePerThread; i++) writeTestDocument(tl, text); latch.countDown(); }).start(); } latch.await(); ExcerptTailer tailer = q.createTailer(); for (int i = 0; i < size; i++) { try (DocumentContext dc = tailer.readingDocument(false)) { long index = dc.index(); long actual = dc.wire().read(() -> "key").int64(); assertEquals(toTextIndex(q, index), toTextIndex(q, actual)); } } } } @NotNull private String toTextIndex(ChronicleQueue q, long index) { return Long.toHexString(q.rollCycle().toCycle(index)) + "_" + Long.toHexString(q.rollCycle().toSequenceNumber(index)); } @Ignore("Long Running Test") @Test public void testRandomConcurrentReadWrite() throws InterruptedException { // some text to simulate load. StringBuilder sb = new StringBuilder(); for (int i = 0; i < 5; i++) sb.append(UUID.randomUUID()); String text = sb.toString(); for (int i = 0; i < 20; i++) { ExecutorService executor = Executors.newWorkStealingPool(8); try (ChronicleQueue q = builder(getTmpDir(), this.wireType) .rollCycle(MINUTELY) .build()) { final ThreadLocal<ExcerptAppender> tl = ThreadLocal.withInitial(q::acquireAppender); final ThreadLocal<ExcerptTailer> tlt = ThreadLocal.withInitial(q::createTailer); int size = 20_000_000; for (int j = 0; j < size; j++) executor.execute(() -> doSomething(tl, tlt, text)); executor.shutdown(); if (!executor.awaitTermination(10_000, TimeUnit.SECONDS)) executor.shutdownNow(); System.out.println(". " + i); Jvm.pause(1000); } } } @Test public void testToEndPrevCycleEOF() { final AtomicLong clock = new AtomicLong(System.currentTimeMillis()); File dir = getTmpDir(); try (ChronicleQueue q = builder(dir, wireType) .rollCycle(TEST_SECONDLY) .timeProvider(clock::get) .build()) { q.acquireAppender() .writeText("first"); } AbstractCloseable.assertCloseablesClosed(); clock.addAndGet(1100); // this will write an EOF try (ChronicleQueue q = builder(dir, wireType) .rollCycle(TEST_SECONDLY) .timeProvider(clock::get) .build()) { ExcerptTailer tailer = q.createTailer(); assertEquals("first", tailer.readText()); assertNull(tailer.readText()); } AbstractCloseable.assertCloseablesClosed(); try (ChronicleQueue q = builder(dir, wireType) .rollCycle(TEST_SECONDLY) .timeProvider(clock::get) .build()) { ExcerptTailer tailer = q.createTailer().toEnd(); try (DocumentContext documentContext = tailer.readingDocument()) { assertFalse(documentContext.isPresent()); } try (DocumentContext documentContext = tailer.readingDocument()) { assertFalse(documentContext.isPresent()); } } AbstractCloseable.assertCloseablesClosed(); clock.addAndGet(50L); try (ChronicleQueue q = builder(dir, wireType) .rollCycle(TEST_SECONDLY) .timeProvider(clock::get) .build()) { ExcerptTailer excerptTailerBeforeAppend = q.createTailer().toEnd(); q.acquireAppender().writeText("more text"); ExcerptTailer excerptTailerAfterAppend = q.createTailer().toEnd(); q.acquireAppender().writeText("even more text"); assertEquals("more text", excerptTailerBeforeAppend.readText()); assertEquals("even more text", excerptTailerAfterAppend.readText()); assertEquals("even more text", excerptTailerBeforeAppend.readText()); } AbstractCloseable.assertCloseablesClosed(); } @Test public void shouldNotGenerateGarbageReadingDocumentAfterEndOfFile() { final AtomicLong clock = new AtomicLong(System.currentTimeMillis()); File dir = getTmpDir(); try (ChronicleQueue q = builder(dir, wireType) .rollCycle(TEST_SECONDLY) .timeProvider(clock::get) .build()) { q.acquireAppender() .writeText("first"); } clock.addAndGet(1100); // this will write an EOF try (ChronicleQueue q = builder(dir, wireType) .rollCycle(TEST_SECONDLY) .timeProvider(clock::get) .build(); ExcerptTailer tailer = q.createTailer()) { assertEquals("first", tailer.readText()); GcControls.waitForGcCycle(); final long startCollectionCount = GcControls.getGcCount(); // allow a few GCs due to possible side-effect or re-used JVM final long maxAllowedGcCycles = 6; final long endCollectionCount = GcControls.getGcCount(); final long actualGcCycles = endCollectionCount - startCollectionCount; assertTrue(String.format("Too many GC cycles. Expected <= %d, but was %d", maxAllowedGcCycles, actualGcCycles), actualGcCycles <= maxAllowedGcCycles); } } @Test public void testTailerWhenCyclesWhereSkippedOnWrite() { SetTimeProvider timeProvider = new SetTimeProvider(); try (final ChronicleQueue queue = binary(getTmpDir()) .rollCycle(RollCycles.TEST_SECONDLY).timeProvider(timeProvider) .build(); final ExcerptAppender appender = queue.acquireAppender(); final ExcerptTailer tailer = queue.createTailer()) { final List<String> stringsToPut = Arrays.asList("one", "two", "three"); // writes two strings immediately and one string with 2 seconds delay { try (DocumentContext writingContext = appender.writingDocument()) { writingContext.wire() .write().bytes(stringsToPut.get(0).getBytes()); } try (DocumentContext writingContext = appender.writingDocument()) { writingContext.wire() .write().bytes(stringsToPut.get(1).getBytes()); } timeProvider.advanceMillis(2100); try (DocumentContext writingContext = appender.writingDocument()) { writingContext.wire().write().bytes(stringsToPut.get(2).getBytes()); } } for (String expected : stringsToPut) { try (DocumentContext readingContext = tailer.readingDocument()) { if (!readingContext.isPresent()) fail(); String text = readingContext.wire().read().text(); assertEquals(expected, text); } } } } private void doSomething(@NotNull ThreadLocal<ExcerptAppender> tla, @NotNull ThreadLocal<ExcerptTailer> tlt, String text) { if (Math.random() > 0.5) writeTestDocument(tla, text); else readDocument(tlt, text); } private void readDocument(@NotNull ThreadLocal<ExcerptTailer> tlt, String text) { try (DocumentContext dc = tlt.get().readingDocument()) { if (!dc.isPresent()) return; assertEquals(dc.index(), dc.wire().read(() -> "key").int64()); assertEquals(text, dc.wire().read(() -> "text").text()); } } private void writeTestDocument(@NotNull ThreadLocal<ExcerptAppender> tl, String text) { try (DocumentContext dc = tl.get().writingDocument()) { long index = dc.index(); dc.wire().write("key").int64(index); dc.wire().write("text").text(text); } } @Test public void testMultipleAppenders() { try (ChronicleQueue syncQ = builder(getTmpDir(), this.wireType) .rollCycle(TEST_DAILY) .build(); ExcerptAppender syncA = syncQ.acquireAppender(); ExcerptAppender syncB = syncQ.acquireAppender(); ExcerptAppender syncC = syncQ.acquireAppender()) { int count = 0; for (int i = 0; i < 3; i++) { syncA.writeText("hello A" + i); assertEquals(count++, (int) syncA.lastIndexAppended()); syncB.writeText("hello B" + i); assertEquals(count++, (int) syncB.lastIndexAppended()); try (DocumentContext dc = syncC.writingDocument(true)) { dc.wire().getValueOut().text("some meta " + i); } } String expected = expectedMultipleAppenders(); assertEquals(expected, syncQ.dump()); } } @NotNull protected String expectedMultipleAppenders() { if (wireType == WireType.BINARY || wireType == WireType.BINARY_LIGHT || wireType == WireType.COMPRESSED_BINARY) return "--- !!meta-data #binary\n" + "header: !SCQStore {\n" + " writePosition: [\n" + " 504,\n" + " 2164663517189\n" + " ],\n" + " indexing: !SCQSIndexing {\n" + " indexCount: 8,\n" + " indexSpacing: 1,\n" + " index2Index: 196,\n" + " lastIndex: 6\n" + " },\n" + " dataFormat: 1\n" + "}\n" + "# position: 196, header: -1\n" + "--- !!meta-data #binary\n" + "index2index: [\n" + " # length: 8, used: 1\n" + " 296,\n" + " 0, 0, 0, 0, 0, 0, 0\n" + "]\n" + "# position: 296, header: -1\n" + "--- !!meta-data #binary\n" + "index: [\n" + " # length: 8, used: 6\n" + " 392,\n" + " 408,\n" + " 440,\n" + " 456,\n" + " 488,\n" + " 504,\n" + " 0, 0\n" + "]\n" + "# position: 392, header: 0\n" + "--- !!data #binary\n" + "hello A0\n" + "# position: 408, header: 1\n" + "--- !!data #binary\n" + "hello B0\n" + "# position: 424, header: 1\n" + "--- !!meta-data #binary\n" + "some meta 0\n" + "# position: 440, header: 2\n" + "--- !!data #binary\n" + "hello A1\n" + "# position: 456, header: 3\n" + "--- !!data #binary\n" + "hello B1\n" + "# position: 472, header: 3\n" + "--- !!meta-data #binary\n" + "some meta 1\n" + "# position: 488, header: 4\n" + "--- !!data #binary\n" + "hello A2\n" + "# position: 504, header: 5\n" + "--- !!data #binary\n" + "hello B2\n" + "# position: 520, header: 5\n" + "--- !!meta-data #binary\n" + "some meta 2\n" + "...\n" + "# 130532 bytes remaining\n"; throw new IllegalStateException("unknown wiretype=" + wireType); } @Test public void testCountExceptsBetweenCycles() { SetTimeProvider timeProvider = new SetTimeProvider(); try (final RollingChronicleQueue queue = binary(getTmpDir()) .rollCycle(RollCycles.TEST_SECONDLY) .timeProvider(timeProvider) .build(); final ExcerptAppender appender = queue.acquireAppender()) { long[] indexs = new long[10]; for (int i = 0; i < indexs.length; i++) { System.out.println("."); try (DocumentContext writingContext = appender.writingDocument()) { writingContext.wire().write().text("some-text-" + i); indexs[i] = writingContext.index(); } // we add the pause times to vary the test, to ensure it can handle when cycles are // skipped if ((i + 1) % 5 == 0) timeProvider.advanceMillis(2000); else if ((i + 1) % 3 == 0) timeProvider.advanceMillis(1000); } for (int lower = 0; lower < indexs.length; lower++) { for (int upper = lower; upper < indexs.length; upper++) { System.out.println("lower=" + lower + ",upper=" + upper); assertEquals(upper - lower, queue.countExcerpts(indexs[lower], indexs[upper])); } } // check the base line of the test below assertEquals(6, queue.countExcerpts(indexs[0], indexs[6])); /// check for the case when the last index has a sequence number of -1 assertEquals(queue.rollCycle().toSequenceNumber(indexs[6]), 0); assertEquals(5, queue.countExcerpts(indexs[0], indexs[6] - 1)); /// check for the case when the first index has a sequence number of -1 assertEquals(7, queue.countExcerpts(indexs[0] - 1, indexs[6])); } } @Test public void testReadingWritingWhenNextCycleIsInSequence() { SetTimeProvider timeProvider = new SetTimeProvider(); final File dir = DirectoryUtils.tempDir(testName.getMethodName()); final RollCycles rollCycle = RollCycles.TEST_SECONDLY; // write first message try (ChronicleQueue queue = binary(dir) .rollCycle(rollCycle).timeProvider(timeProvider).build()) { queue.acquireAppender().writeText("first message"); } timeProvider.advanceMillis(1100); // write second message try (ChronicleQueue queue = binary(dir) .rollCycle(rollCycle).timeProvider(timeProvider).build()) { queue.acquireAppender().writeText("second message"); } // read both messages try (ChronicleQueue queue = binary(dir) .rollCycle(rollCycle).timeProvider(timeProvider).build(); ExcerptTailer tailer = queue.createTailer()) { assertEquals("first message", tailer.readText()); assertEquals("second message", tailer.readText()); } } @Test public void testReadingWritingWhenCycleIsSkipped() { SetTimeProvider timeProvider = new SetTimeProvider(); final File dir = DirectoryUtils.tempDir(testName.getMethodName()); final RollCycles rollCycle = RollCycles.TEST_SECONDLY; // write first message try (ChronicleQueue queue = binary(dir) .rollCycle(rollCycle).timeProvider(timeProvider) .build()) { queue.acquireAppender().writeText("first message"); } timeProvider.advanceMillis(2100); // write second message try (ChronicleQueue queue = binary(dir) .rollCycle(rollCycle).timeProvider(timeProvider).build()) { queue.acquireAppender().writeText("second message"); } // read both messages try (ChronicleQueue queue = binary(dir) .rollCycle(rollCycle).timeProvider(timeProvider).build(); ExcerptTailer tailer = queue.createTailer()) { assertEquals("first message", tailer.readText()); assertEquals("second message", tailer.readText()); } } @Test public void testReadingWritingWhenCycleIsSkippedBackwards() { final SetTimeProvider timeProvider = new SetTimeProvider(); long time = System.currentTimeMillis(); timeProvider.currentTimeMillis(time); final File dir = DirectoryUtils.tempDir(testName.getMethodName()); final RollCycles rollCycle = RollCycles.TEST_SECONDLY; // write first message try (ChronicleQueue queue = binary(dir).rollCycle(rollCycle).timeProvider(timeProvider).build()) { queue.acquireAppender().writeText("first message"); } timeProvider.advanceMillis(2100); // write second message try (ChronicleQueue queue = binary(dir).rollCycle(rollCycle).timeProvider(timeProvider).build()) { queue.acquireAppender().writeText("second message"); } // read both messages try (ChronicleQueue queue = binary(dir).rollCycle(rollCycle).timeProvider(timeProvider).build(); ExcerptTailer tailer = queue.createTailer()) { ExcerptTailer excerptTailer = tailer.direction(TailerDirection.BACKWARD).toEnd(); assertEquals("second message", excerptTailer.readText()); assertEquals("first message", excerptTailer.readText()); } } @Test public void testReadWritingWithTimeProvider() { final File dir = DirectoryUtils.tempDir(testName.getMethodName()); long time = System.currentTimeMillis(); SetTimeProvider timeProvider = new SetTimeProvider(); timeProvider.currentTimeMillis(time); try (ChronicleQueue q1 = binary(dir) .timeProvider(timeProvider) .build()) { try (ChronicleQueue q2 = binary(dir) .timeProvider(timeProvider) .build(); final ExcerptAppender appender2 = q2.acquireAppender(); final ExcerptTailer tailer1 = q1.createTailer(); final ExcerptTailer tailer2 = q2.createTailer()) { try (final DocumentContext dc = appender2.writingDocument()) { dc.wire().write().text("some data"); } try (DocumentContext dc = tailer2.readingDocument()) { assertTrue(dc.isPresent()); } assertEquals(q1.file(), q2.file()); // this is required for queue to re-request last/first cycle timeProvider.advanceMillis(1); for (int i = 0; i < 10; i++) { try (DocumentContext dc = tailer1.readingDocument()) { if (dc.isPresent()) return; } Jvm.pause(1); } fail(); } } } @Test public void testTailerSnappingRollWithNewAppender() throws InterruptedException, ExecutionException, TimeoutException { expectException(""); SetTimeProvider timeProvider = new SetTimeProvider(); timeProvider.currentTimeMillis(System.currentTimeMillis() - 2_000); final File dir = DirectoryUtils.tempDir(testName.getMethodName()); final RollCycles rollCycle = RollCycles.TEST_SECONDLY; // write first message try (ChronicleQueue queue = binary(dir) .rollCycle(rollCycle).timeProvider(timeProvider).build()) { ExcerptAppender excerptAppender = queue.acquireAppender(); excerptAppender.writeText("someText"); ExecutorService executorService = Executors.newFixedThreadPool(2, new NamedThreadFactory("test")); Future<?> f1 = executorService.submit(() -> { try (ChronicleQueue queue2 = binary(dir) .rollCycle(rollCycle).timeProvider(timeProvider).build()) { queue2.acquireAppender().writeText("someText more"); } timeProvider.advanceMillis(1100); try (ChronicleQueue queue2 = binary(dir) .rollCycle(rollCycle).build()) { queue2.acquireAppender().writeText("someText more"); } }); Future<?> f2 = executorService.submit(() -> { // write second message try (ChronicleQueue queue2 = binary(dir) .rollCycle(rollCycle).timeProvider(timeProvider).build()) { for (int i = 0; i < 5; i++) { queue2.acquireAppender().writeText("someText more"); timeProvider.advanceMillis(400); } } }); f1.get(10, TimeUnit.SECONDS); // System.out.println(queue.dump()); f2.get(10, TimeUnit.SECONDS); executorService.shutdownNow(); } } @Test public void testLongLivingTailerAppenderReAcquiredEachSecond() { SetTimeProvider timeProvider = new SetTimeProvider(); final File dir = DirectoryUtils.tempDir(testName.getMethodName()); final RollCycles rollCycle = RollCycles.TEST_SECONDLY; try (ChronicleQueue queuet = binary(dir) .rollCycle(rollCycle) .timeProvider(timeProvider) .build(); final ExcerptTailer tailer = queuet.createTailer()) { // write first message try (ChronicleQueue queue = binary(dir) .rollCycle(rollCycle) .timeProvider(timeProvider) .build()) { for (int i = 0; i < 5; i++) { final ExcerptAppender appender = queue.acquireAppender(); timeProvider.advanceMillis(1100); try (final DocumentContext dc = appender.writingDocument()) { dc.wire().write("some").int32(i); } try (final DocumentContext dc = tailer.readingDocument()) { assertEquals(i, dc.wire().read("some").int32()); } } } } } @Test(expected = IllegalStateException.class) public void testCountExceptsWithRubbishData() { try (final RollingChronicleQueue queue = binary(getTmpDir()) .rollCycle(RollCycles.TEST_SECONDLY) .build()) { // rubbish data queue.countExcerpts(0x578F542D00000000L, 0x528F542D00000000L); } } @Test public void testFromSizePrefixedBlobs() { try (final ChronicleQueue queue = binary(getTmpDir()) .build()) { try (DocumentContext dc = queue.acquireAppender().writingDocument()) { dc.wire().write("some").text("data"); } String s = null; DocumentContext dc0; try (DocumentContext dc = queue.createTailer().readingDocument()) { s = Wires.fromSizePrefixedBlobs(dc); if (!encryption) assertTrue(s.contains("some: data")); dc0 = dc; } String out = Wires.fromSizePrefixedBlobs(dc0); assertEquals(s, out); } } @Test public void tailerRollBackTest() { final File source = DirectoryUtils.tempDir("testCopyQueue-source"); try (final ChronicleQueue q = binary(source).build()) { try (DocumentContext dc = q.acquireAppender().writingDocument()) { dc.wire().write("hello").text("hello-world"); } try (DocumentContext dc = q.acquireAppender().writingDocument()) { dc.wire().write("hello2").text("hello-world-2"); } } } @Test public void testCopyQueue() { final File source = DirectoryUtils.tempDir("testCopyQueue-source"); final File target = DirectoryUtils.tempDir("testCopyQueue-target"); { try (final ChronicleQueue q = binary(source) .build(); ExcerptAppender excerptAppender = q.acquireAppender()) { excerptAppender.writeMessage(() -> "one", 1); excerptAppender.writeMessage(() -> "two", 2); excerptAppender.writeMessage(() -> "three", 3); excerptAppender.writeMessage(() -> "four", 4); } } { try (final ChronicleQueue s = binary(source).build(); final ChronicleQueue t = binary(target).build(); ExcerptTailer sourceTailer = s.createTailer(); ExcerptAppender appender = t.acquireAppender()) { for (; ; ) { try (DocumentContext rdc = sourceTailer.readingDocument()) { if (!rdc.isPresent()) break; try (DocumentContext wdc = appender.writingDocument()) { final Bytes<?> bytes = rdc.wire().bytes(); wdc.wire().bytes().write(bytes); } } } } } } /** * see https://github.com/OpenHFT/Chronicle-Queue/issues/299 */ @Test public void testIncorrectExcerptTailerReadsAfterSwitchingTailerDirection() { try (final ChronicleQueue queue = binary(getTmpDir()) .rollCycle(RollCycles.TEST_SECONDLY).build()) { int value = 0; long cycle = 0; long startIndex = 0; for (int i = 0; i < 56; i++) { try (final DocumentContext dc = queue.acquireAppender().writingDocument()) { if (cycle == 0) cycle = queue.rollCycle().toCycle(dc.index()); final long index = dc.index(); final long seq = queue.rollCycle().toSequenceNumber(index); if (seq == 52) startIndex = dc.index(); if (seq >= 52) { final int v = value++; dc.wire().write("value").int64(v); } else { dc.wire().write("value").int64(0); } } } try (ExcerptTailer tailer = queue.createTailer()) { assertTrue(tailer.moveToIndex(startIndex)); tailer.direction(TailerDirection.FORWARD); assertEquals(0, action(tailer, queue.rollCycle())); assertEquals(1, action(tailer, queue.rollCycle())); tailer.direction(TailerDirection.BACKWARD); assertEquals(2, action(tailer, queue.rollCycle())); assertEquals(1, action(tailer, queue.rollCycle())); tailer.direction(TailerDirection.FORWARD); assertEquals(0, action(tailer, queue.rollCycle())); assertEquals(1, action(tailer, queue.rollCycle())); } } } @Test public void testExistingRollCycleIsMaintained() { RollCycles[] values = values(); for (int i = 0; i < values.length - 1; i++) { final File tmpDir = getTmpDir(); try (final ChronicleQueue queue = binary(tmpDir) .rollCycle(values[i]).build()) { queue.acquireAppender().writeText("hello world"); } try (final ChronicleQueue queue = binary(tmpDir) .rollCycle(values[i + 1]).build()) { assertEquals(values[i], queue.rollCycle()); } } } private long action(@NotNull final ExcerptTailer tailer1, @NotNull final RollCycle rollCycle) { try (final DocumentContext dc = tailer1.readingDocument()) { return dc.wire().read("value").int64(); } finally { rollCycle.toSequenceNumber(tailer1.index()); } } @Test public void checkReferenceCountingAndCheckFileDeletion() { MappedFile mappedFile; try (ChronicleQueue queue = binary(getTmpDir()) .rollCycle(RollCycles.TEST_SECONDLY) .build()) { ExcerptAppender appender = queue.acquireAppender(); try (DocumentContext documentContext1 = appender.writingDocument()) { documentContext1.wire().write().text("some text"); } try (DocumentContext documentContext = queue.createTailer().readingDocument()) { mappedFile = toMappedFile(documentContext); assertEquals("some text", documentContext.wire().read().text()); } } waitFor(mappedFile::isClosed, "mappedFile is not closed"); if (OS.isWindows()) { System.err.println("#460 Cannot test delete after close on windows"); return; } // this used to fail on windows assertTrue(mappedFile.file().delete()); } @Test public void checkReferenceCountingWhenRollingAndCheckFileDeletion() { SetTimeProvider timeProvider = new SetTimeProvider(); @SuppressWarnings("unused") MappedFile mappedFile1, mappedFile2, mappedFile3, mappedFile4; try (ChronicleQueue queue = binary(getTmpDir()) .rollCycle(RollCycles.TEST_SECONDLY) .timeProvider(timeProvider) .build(); ExcerptAppender appender = queue.acquireAppender()) { try (DocumentContext dc = appender.writingDocument()) { dc.wire().write().text("some text"); mappedFile1 = toMappedFile(dc); } timeProvider.advanceMillis(1100); try (DocumentContext dc = appender.writingDocument()) { dc.wire().write().text("some more text"); mappedFile2 = toMappedFile(dc); } try (ExcerptTailer tailer = queue.createTailer()) { try (DocumentContext documentContext = tailer.readingDocument()) { mappedFile3 = toMappedFile(documentContext); assertEquals("some text", documentContext.wire().read().text()); } try (DocumentContext documentContext = tailer.readingDocument()) { mappedFile4 = toMappedFile(documentContext); assertEquals("some more text", documentContext.wire().read().text()); } } } waitFor(mappedFile1::isClosed, "mappedFile1 is not closed"); waitFor(mappedFile2::isClosed, "mappedFile2 is not closed"); if (OS.isWindows()) { System.err.println("#460 Cannot test delete after close on windows"); return; } // this used to fail on windows assertTrue(mappedFile1.file().delete()); assertTrue(mappedFile2.file().delete()); } @Test(timeout = 10_000) public void testWritingDocumentIsAtomic() { final int threadCount = 8; final ExecutorService executorService = Executors.newFixedThreadPool(threadCount, new NamedThreadFactory("test")); // remove change of cycle roll in test, cross-cycle atomicity is covered elsewhere final AtomicLong fixedClock = new AtomicLong(System.currentTimeMillis()); try (ChronicleQueue queue = ChronicleQueue.singleBuilder(getTmpDir()) .rollCycle(RollCycles.TEST_SECONDLY) .timeoutMS(3_000) .timeProvider(fixedClock::get) .testBlockSize() .build()) { final int iterationsPerThread = Short.MAX_VALUE / 8; final int totalIterations = iterationsPerThread * threadCount; final int[] nonAtomicCounter = new int[]{0}; for (int i = 0; i < threadCount; i++) { executorService.submit(() -> { for (int j = 0; j < iterationsPerThread; j++) { ExcerptAppender excerptAppender = queue.acquireAppender(); try (DocumentContext dc = excerptAppender.writingDocument()) { int value = nonAtomicCounter[0]++; dc.wire().write("some key").int64(value); } } }); } long timeout = 20_000 + System.currentTimeMillis(); ExcerptTailer tailer = queue.createTailer(); for (int expected = 0; expected < totalIterations; expected++) { for (; ; ) { if (System.currentTimeMillis() > timeout) fail("Timed out, having read " + expected + " documents of " + totalIterations); try (DocumentContext dc = tailer.readingDocument()) { if (!dc.isPresent()) { Thread.yield(); continue; } long justRead = dc.wire().read("some key").int64(); assertEquals(expected, justRead); break; } } } } finally { executorService.shutdownNow(); try { executorService.awaitTermination(1, TimeUnit.SECONDS); } catch (InterruptedException e) { executorService.shutdownNow(); } } } @Test public void shouldBeAbleToLoadQueueFromReadOnlyFiles() throws IOException { if (OS.isWindows()) { System.err.println("#460 Cannot test read only mode on windows"); return; } final File queueDir = getTmpDir(); try (final ChronicleQueue queue = builder(queueDir, wireType). testBlockSize().build()) { queue.acquireAppender().writeDocument("foo", (v, t) -> { v.text(t); }); } Files.list(queueDir.toPath()) .forEach(p -> assertTrue(p.toFile().setReadOnly())); try (final ChronicleQueue queue = builder(queueDir, wireType). readOnly(true). testBlockSize().build()) { assertTrue(queue.createTailer().readingDocument().isPresent()); } } @Test public void shouldCreateQueueInCurrentDirectory() { if (OS.isWindows()) { System.err.println("#460 Cannot test delete after close on windows"); return; } try (final ChronicleQueue ignored = builder(new File(""), wireType). testBlockSize().build()) { } assertTrue(new File(QUEUE_METADATA_FILE).delete()); } @NotNull protected SingleChronicleQueueBuilder builder(@NotNull File file, @NotNull WireType wireType) { return SingleChronicleQueueBuilder.builder(file, wireType).rollCycle(RollCycles.TEST4_DAILY).testBlockSize(); } @NotNull protected SingleChronicleQueueBuilder binary(@NotNull File file) { return builder(file, WireType.BINARY_LIGHT); } private MappedFile toMappedFile(@NotNull DocumentContext documentContext) { MappedFile mappedFile; MappedBytes bytes = (MappedBytes) documentContext.wire().bytes(); mappedFile = bytes.mappedFile(); return mappedFile; } @Test public void writeBytesAndIndexFiveTimesWithOverwriteTest() { try (final ChronicleQueue sourceQueue = builder(DirectoryUtils.tempDir("to-be-deleted"), wireType). testBlockSize().build()) { for (int i = 0; i < 5; i++) { ExcerptAppender excerptAppender = sourceQueue.acquireAppender(); try (DocumentContext dc = excerptAppender.writingDocument()) { dc.wire().write("hello").text("world" + i); } } try (ExcerptTailer tailer = sourceQueue.createTailer(); ChronicleQueue queue = builder(DirectoryUtils.tempDir("to-be-deleted"), wireType).testBlockSize().build()) { ExcerptAppender appender0 = queue.acquireAppender(); if (!(appender0 instanceof InternalAppender)) return; InternalAppender appender = (InternalAppender) appender0; if (!(appender instanceof StoreAppender)) return; List<BytesWithIndex> bytesWithIndies = new ArrayList<>(); try { for (int i = 0; i < 5; i++) { bytesWithIndies.add(bytes(tailer)); } for (int i = 0; i < 4; i++) { BytesWithIndex b = bytesWithIndies.get(i); appender.writeBytes(b.index, b.bytes); } for (int i = 0; i < 4; i++) { BytesWithIndex b = bytesWithIndies.get(i); appender.writeBytes(b.index, b.bytes); } BytesWithIndex b = bytesWithIndies.get(4); appender.writeBytes(b.index, b.bytes); ((StoreAppender) appender).checkWritePositionHeaderNumber(); appender0.writeText("hello"); } finally { closeQuietly(bytesWithIndies); } String dump = queue.dump(); assertTrue(dump, dump.contains( "# position: 776, header: 0\n" + "--- !!data #binary\n" + "hello: world0\n" + "# position: 796, header: 1\n" + "--- !!data #binary\n" + "hello: world1\n" + "# position: 816, header: 2\n" + "--- !!data #binary\n" + "hello: world2\n" + "# position: 836, header: 3\n" + "--- !!data #binary\n" + "hello: world3\n" + "# position: 856, header: 4\n" + "--- !!data #binary\n" + "hello: world4\n" + "# position: 876, header: 5\n" + "--- !!data #binary\n" + "hello\n")); } } } @Test public void writeBytesAndIndexFiveTimesTest() { try (final ChronicleQueue sourceQueue = builder(DirectoryUtils.tempDir("to-be-deleted"), wireType). testBlockSize().build()) { for (int i = 0; i < 5; i++) { ExcerptAppender excerptAppender = sourceQueue.acquireAppender(); try (DocumentContext dc = excerptAppender.writingDocument()) { dc.wire().write("hello").text("world" + i); } } String before = sourceQueue.dump(); try (ExcerptTailer tailer = sourceQueue.createTailer(); ChronicleQueue queue = builder(DirectoryUtils.tempDir("to-be-deleted"), wireType).testBlockSize().build()) { ExcerptAppender appender = queue.acquireAppender(); if (!(appender instanceof StoreAppender)) return; for (int i = 0; i < 5; i++) { try (final BytesWithIndex b = bytes(tailer)) { ((InternalAppender) appender).writeBytes(b.index, b.bytes); } } String dump = queue.dump(); assertEquals(before, dump); assertTrue(dump, dump.contains("# position: 776, header: 0\n" + "--- !!data #binary\n" + "hello: world0\n" + "# position: 796, header: 1\n" + "--- !!data #binary\n" + "hello: world1\n" + "# position: 816, header: 2\n" + "--- !!data #binary\n" + "hello: world2\n" + "# position: 836, header: 3\n" + "--- !!data #binary\n" + "hello: world3\n" + "# position: 856, header: 4\n" + "--- !!data #binary\n" + "hello: world4")); } } } @Test public void rollbackTest() { File file = DirectoryUtils.tempDir("to-be-deleted"); try (final ChronicleQueue sourceQueue = builder(file, wireType). testBlockSize().build(); ExcerptAppender excerptAppender = sourceQueue.acquireAppender()) { try (DocumentContext dc = excerptAppender.writingDocument()) { dc.wire().write("hello").text("world1"); } try (DocumentContext dc = excerptAppender.writingDocument()) { dc.wire().write("hello2").text("world2"); } try (DocumentContext dc = excerptAppender.writingDocument()) { dc.wire().write("hello3").text("world3"); } } try (final ChronicleQueue queue = builder(file, wireType).testBlockSize().build(); ExcerptTailer tailer1 = queue.createTailer()) { StringBuilder sb = Wires.acquireStringBuilder(); try (DocumentContext documentContext = tailer1.readingDocument()) { documentContext.wire().readEventName(sb); assertEquals("hello", sb.toString()); documentContext.rollbackOnClose(); } try (DocumentContext documentContext = tailer1.readingDocument()) { documentContext.wire().readEventName(sb); assertEquals("hello", sb.toString()); } try (DocumentContext documentContext = tailer1.readingDocument()) { documentContext.wire().readEventName(sb); documentContext.rollbackOnClose(); assertEquals("hello2", sb.toString()); } try (DocumentContext documentContext = tailer1.readingDocument()) { Bytes<?> bytes = documentContext.wire().bytes(); long rp = bytes.readPosition(); long wp = bytes.writePosition(); long wl = bytes.writeLimit(); try { documentContext.wire().readEventName(sb); assertEquals("hello2", sb.toString()); documentContext.rollbackOnClose(); } finally { bytes.readPosition(rp).writePosition(wp).writeLimit(wl); } } try (DocumentContext documentContext = tailer1.readingDocument()) { documentContext.wire().readEventName(sb); assertEquals("hello2", sb.toString()); } try (DocumentContext documentContext = tailer1.readingDocument()) { documentContext.wire().readEventName(sb); assertEquals("hello3", sb.toString()); documentContext.rollbackOnClose(); } try (DocumentContext documentContext = tailer1.readingDocument()) { assertTrue(documentContext.isPresent()); documentContext.wire().readEventName(sb); assertEquals("hello3", sb.toString()); } try (DocumentContext documentContext = tailer1.readingDocument()) { assertFalse(documentContext.isPresent()); documentContext.rollbackOnClose(); } try (DocumentContext documentContext = tailer1.readingDocument()) { assertFalse(documentContext.isPresent()); } } } private BytesWithIndex bytes(final ExcerptTailer tailer) { try (DocumentContext dc = tailer.readingDocument()) { if (!dc.isPresent()) return null; Bytes<?> bytes = dc.wire().bytes(); long index = dc.index(); return new BytesWithIndex(bytes, index); } } @Ignore("TODO FIX https://github.com/OpenHFT/Chronicle-Core/issues/121") @Test public void mappedSegmentsShouldBeUnmappedAsCycleRolls() throws IOException, InterruptedException { Assume.assumeTrue("this test is slow and does not depend on wire type", wireType == WireType.BINARY); long now = System.currentTimeMillis(); long ONE_HOUR_IN_MILLIS = 60 * 60 * 1000; long ONE_DAY_IN_MILLIS = ONE_HOUR_IN_MILLIS * 24; long midnight = now - (now % ONE_DAY_IN_MILLIS); AtomicLong clock = new AtomicLong(now); StringBuilder builder = new StringBuilder(); boolean passed = doMappedSegmentUnmappedRollTest(clock, builder); passed = passed && doMappedSegmentUnmappedRollTest(setTime(clock, midnight), builder); for (int i = 1; i < 3; i += 1) passed = passed && doMappedSegmentUnmappedRollTest(setTime(clock, midnight + (i * ONE_HOUR_IN_MILLIS)), builder); if (!passed) { fail(builder.toString()); } } private AtomicLong setTime(AtomicLong clock, long newValue) { clock.set(newValue); return clock; } private boolean doMappedSegmentUnmappedRollTest(AtomicLong clock, StringBuilder builder) throws IOException, InterruptedException { String time = Instant.ofEpochMilli(clock.get()).toString(); final Random random = new Random(0xDEADBEEF); final File queueFolder = DirectoryUtils.tempDir("mappedSegmentsShouldBeUnmappedAsCycleRolls"); try (final ChronicleQueue queue = ChronicleQueue.singleBuilder(queueFolder). timeProvider(clock::get). testBlockSize().rollCycle(RollCycles.HOURLY). build(); ExcerptAppender appender = queue.acquireAppender()) { for (int i = 0; i < 20_000; i++) { final int batchSize = random.nextInt(10); appender.writeDocument(batchSize, ValueOut::int64); final byte payload = (byte) random.nextInt(); for (int j = 0; j < batchSize; j++) { appender.writeDocument(payload, ValueOut::int8); } if (random.nextDouble() > 0.995) { clock.addAndGet(TimeUnit.MINUTES.toMillis(37L)); // this give the reference processor a chance to run Jvm.pause(30); } } boolean passed = true; if (OS.isLinux()) { List<String> openFiles = getMappedQueueFileCount(); int filesOpen = openFiles.size(); if (filesOpen >= 50) { passed = false; builder.append(String.format("Test for time %s failed: Too many mapped files: %d%n", time, filesOpen)); builder.append("Open files:").append("\n"); openFiles.stream().map(s -> s + "\n").forEach(builder::append); } } long fileCount = Files.list(queueFolder.toPath()).filter(p -> p.toString().endsWith(SUFFIX)).count(); if (fileCount <= 10L) { passed = false; builder.append(String.format("Test for time %s failed: Too many mapped files: %d%n", time, fileCount)); } if (passed) { builder.append(String.format("Test for time %s passed!%n", time)); } return passed; } } interface Msg { void msg(String s); } private static class MapWrapper extends SelfDescribingMarshallable { final Map<CharSequence, Double> map = new HashMap<>(); } static class MyMarshable extends SelfDescribingMarshallable implements Demarshallable { @UsedViaReflection String name; @UsedViaReflection public MyMarshable(@NotNull WireIn wire) { readMarshallable(wire); } public MyMarshable() { } } private static class BytesWithIndex implements Closeable { private BytesStore bytes; private long index; public BytesWithIndex(Bytes<?> bytes, long index) { this.bytes = Bytes.allocateElasticDirect(bytes.readRemaining()).write(bytes); this.index = index; } @Override public void close() { bytes.releaseLast(); } } }