From 53c4abd0d0fc107f756b10021f52ed829cd5a407 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Wed, 29 Apr 2026 14:45:52 +0100 Subject: [PATCH 1/7] docs: normalise non-ISO characters --- README.adoc | 12 ++++++------ docs/FAQ.adoc | 2 +- docs/antora/modules/ROOT/pages/index.adoc | 6 +++--- docs/antora/modules/appending/pages/appending.adoc | 4 ++-- docs/antora/modules/async-mode/nav.adoc | 2 +- .../command-line/pages/command_line_tools.adoc | 4 ++-- docs/antora/modules/configuration/nav.adoc | 4 ++-- .../modules/configuration/pages/buffer-modes.adoc | 2 +- .../modules/configuration/pages/roll-cycle.adoc | 10 +++++----- docs/antora/modules/encryption/nav.adoc | 2 +- .../modules/introduction/pages/introduction.adoc | 6 +++--- docs/antora/modules/performance/nav.adoc | 2 +- .../modules/performance/pages/performance.adoc | 2 +- .../modules/performance/pages/pretouching.adoc | 4 ++-- .../modules/queue-operations/pages/appending.adoc | 4 ++-- .../modules/queue-operations/pages/indexing.adoc | 2 +- .../modules/queue-operations/pages/tailing.adoc | 9 +-------- docs/antora/modules/replication/nav.adoc | 2 +- docs/antora/modules/tailing/pages/tailing.adoc | 9 +-------- .../net/openhft/chronicle/queue/ExcerptTailer.java | 2 +- .../chronicle/queue/impl/WireStoreFactory.java | 4 ++-- 21 files changed, 40 insertions(+), 54 deletions(-) diff --git a/README.adoc b/README.adoc index 48643bc8af..921e6dddbf 100644 --- a/README.adoc +++ b/README.adoc @@ -692,7 +692,7 @@ We refer to this 4-byte header, along with your excerpt, as a document. Strictly speaking Chronicle Queue can be used to read and write documents. NOTE: Within this 4-byte header we also reserve a few bits for a number of internal operations, such as locking, to make Chronicle Queue thread-safe across both processors and threads. -The important thing to note is that because of this, you can’t strictly convert the 4 bytes to an integer to find the length of your data blob. +The important thing to note is that because of this, you can't strictly convert the 4 bytes to an integer to find the length of your data blob. As stated before, Chronicle Queue uses an _appender_ to write to the queue and a _tailer_ to read from the queue. Unlike other java queuing solutions, messages are not lost when they are read with a tailer. @@ -711,7 +711,7 @@ Chronicle Queue uses the following low-level interface to write the data: [source,java] ---- try (final DocumentContext dc = appender.writingDocument()) { - dc.wire().write().text(“your text data“); + dc.wire().write().text("your text data"); } ---- @@ -723,7 +723,7 @@ Each Chronicle Queue excerpt has a unique index. [source,java] ---- try (final DocumentContext dc = appender.writingDocument()) { - dc.wire().write().text(“your text data“); + dc.wire().write().text("your text data"); System.out.println("your data was store to index="+ dc.index()); } ---- @@ -1570,15 +1570,15 @@ The default value is 0. ''' ==== File handles and flushing data to the disk -As mentioned previously Chronicle Queue stores its data off-heap in a ‘.cq4’ file. +As mentioned previously Chronicle Queue stores its data off-heap in a '.cq4' file. So whenever you wish to append data to this file or read data into this file, chronicle queue will create a file handle. -Typically, Chronicle Queue will create a new ‘.cq4’ file every day. +Typically, Chronicle Queue will create a new '.cq4' file every day. However, this could be changed so that you can create a new file every hour, every minute or even every second. If we create a queue file every second, we would refer to this as SECONDLY rolling. Of course, creating a new file every second is a little extreme, but it's a good way to illustrate the following point. When using secondly rolling, If you had written 10 seconds worth of data and then you wish to read this data, chronicle would have to scan across 10 files. -To reduce the creation of the file handles, chronicle queue cashes them lazily and when it comes to writing data to the queue files, care-full consideration must be taken when closing the files, because on most OS’s a close of the file, will force any data that has been appended to the file, to be flushed to disk, and if we are not careful this could stall your application. +To reduce the creation of the file handles, chronicle queue cashes them lazily and when it comes to writing data to the queue files, care-full consideration must be taken when closing the files, because on most OS's a close of the file, will force any data that has been appended to the file, to be flushed to disk, and if we are not careful this could stall your application. ''' ==== Pretoucher and its configuration diff --git a/docs/FAQ.adoc b/docs/FAQ.adoc index fc8380c6a6..c0d1dcdc4d 100644 --- a/docs/FAQ.adoc +++ b/docs/FAQ.adoc @@ -394,7 +394,7 @@ try (DocumentContext dc = appender.writingDocument()) { If you are writing bytes to a Chronicle Queue you will find that it occasionally adds padding to the end of each message. This is to ensure that each message starts on a 4-byte boundary which is a requirement for ARM architectures. -NOTE: Intel requires that messages don’t straggle 64-byte cash lines. +NOTE: Intel requires that messages don't straggle 64-byte cash lines. but aligning to 4 bytes also ensures 64-byte alignment and allows your Chronicle Queues to be shared between various different platforms. For Chronicle Queue, the 4-byte alignment is now enforced, so there is now, no way to turn this feature on or off. diff --git a/docs/antora/modules/ROOT/pages/index.adoc b/docs/antora/modules/ROOT/pages/index.adoc index eb6c277328..c796e7ca6c 100644 --- a/docs/antora/modules/ROOT/pages/index.adoc +++ b/docs/antora/modules/ROOT/pages/index.adoc @@ -33,10 +33,10 @@ Tailing :: Learn how to read from a queue. Performance tuning :: As every environment is different, this chapter points out key factors to obtain the best performance with Chronicle Queue. -★ Replication :: +Replication (Enterprise) :: Replication is an Enterprise feature allowing queues to be automatically replicated across machines or cores. This chapter teaches how that is accomplished. -★ Encryption :: +Encryption (Enterprise) :: Learn how to protect the contents of a Chronicle Queue with encryption. === Runnable Demos @@ -74,7 +74,7 @@ The following features are implemented by Chronicle Queue Enterprise: * Multi-master queues which are ordered for events in a node, but not between nodes. This documentation covers the open source functionality but is also extended by a few chapters describing enterprise specific features. -These sections are clearly marked with a star-symbol (★) and include: +These sections are clearly marked with an Enterprise marker and include: * Encryption of message queues and messages. diff --git a/docs/antora/modules/appending/pages/appending.adoc b/docs/antora/modules/appending/pages/appending.adoc index 3a3e31a5d0..1f212f5217 100644 --- a/docs/antora/modules/appending/pages/appending.adoc +++ b/docs/antora/modules/appending/pages/appending.adoc @@ -33,7 +33,7 @@ Chronicle Queue uses the following low-level interface to write the data: [source,java] ---- try (final DocumentContext dc = appender.writingDocument()) { - dc.wire().write().text(“your text data“); + dc.wire().write().text("your text data"); } ---- @@ -45,7 +45,7 @@ Each Chronicle Queue excerpt has a unique index. [source,java] ---- try (final DocumentContext dc = appender.writingDocument()) { - dc.wire().write().text(“your text data“); + dc.wire().write().text("your text data"); System.out.println("your data was store to index="+ dc.index()); } ---- diff --git a/docs/antora/modules/async-mode/nav.adoc b/docs/antora/modules/async-mode/nav.adoc index 74fa1c4b43..1f42120cbe 100644 --- a/docs/antora/modules/async-mode/nav.adoc +++ b/docs/antora/modules/async-mode/nav.adoc @@ -1 +1 @@ -* xref:async_mode.adoc[Async Mode ★] +* xref:async_mode.adoc[Async Mode (Enterprise)] diff --git a/docs/antora/modules/command-line/pages/command_line_tools.adoc b/docs/antora/modules/command-line/pages/command_line_tools.adoc index 00bb070fe3..d1d8fb594d 100644 --- a/docs/antora/modules/command-line/pages/command_line_tools.adoc +++ b/docs/antora/modules/command-line/pages/command_line_tools.adoc @@ -12,7 +12,7 @@ The following command line tools can be used to interact with Chronicle Queue. [cols="1,1"] |=== -| ★ <<_archiverollfiles,*ArchiveRollFiles*>> +| <<_archiverollfiles,*ArchiveRollFiles*>> (Enterprise) | Assists in management of roll cycle files | <<_chroniclehistoryreadermain,*ChronicleHistoryReaderMain*>> @@ -47,7 +47,7 @@ $ mvn exec:java -Dexec.mainClass="net.openhft.chronicle.queue.main.DumpMain" -De In the above command invoking *DumpMain*, _myqueue_ is the directory containing your .cq4 files. -== ★ ArchiveRollFiles +== ArchiveRollFiles (Enterprise) To facilitate housekeeping of older roll files, Chronicle Queue Enterprise provides the `ArchiveRollFiles` utility. diff --git a/docs/antora/modules/configuration/nav.adoc b/docs/antora/modules/configuration/nav.adoc index 301c18bda4..a7d3e68757 100644 --- a/docs/antora/modules/configuration/nav.adoc +++ b/docs/antora/modules/configuration/nav.adoc @@ -1,8 +1,8 @@ * xref:app_configuration.adoc[Configuration] ** xref:roll-cycle.adoc[Roll Cycle] -** xref:timezone_rollover.adoc[Timezone Rollover ★] +** xref:timezone_rollover.adoc[Timezone Rollover (Enterprise)] ** xref:wire-type.adoc[Wire Type] ** xref:block-size.adoc[Block Size] ** xref:index-count.adoc[Index Count] ** xref:index-spacing.adoc[Index Spacing] -** xref:buffer-modes.adoc[Buffer Modes ★] +** xref:buffer-modes.adoc[Buffer Modes (Enterprise)] diff --git a/docs/antora/modules/configuration/pages/buffer-modes.adoc b/docs/antora/modules/configuration/pages/buffer-modes.adoc index eca57e9503..6032be959e 100644 --- a/docs/antora/modules/configuration/pages/buffer-modes.adoc +++ b/docs/antora/modules/configuration/pages/buffer-modes.adoc @@ -1,4 +1,4 @@ -= ★ Buffer Modes += Buffer Modes (Enterprise) keywords: buffer modes, chronicle queue, queue, java author: Julia Gustafsson :reftext: Buffer modes diff --git a/docs/antora/modules/configuration/pages/roll-cycle.adoc b/docs/antora/modules/configuration/pages/roll-cycle.adoc index 7631032fe5..6ea23f3a30 100644 --- a/docs/antora/modules/configuration/pages/roll-cycle.adoc +++ b/docs/antora/modules/configuration/pages/roll-cycle.adoc @@ -46,7 +46,7 @@ NOTE: The default roll-cycle is `RollCycles.FAST_DAILY`. === Overriding the roll cycle -Once a queue’s roll-cycle has been set, it cannot be changed at a later time. +Once a queue's roll-cycle has been set, it cannot be changed at a later time. Once a queue's metadata has been persisted as part of creating the queue, any further instances of `SingleChronicleQueue` configured to use the same path will have their configuration overridden to match the persisted roll-cycle. Chronicle Queue will notify of the override via a warning log message: @@ -62,7 +62,7 @@ In this case, the persisted roll-cycle is `FAST_HOURLY`, and a new `SingleChroni Chronicle Queue is UTC time based and uses `System.currentTimeMillis()` to evaluate when it is time to roll the queue. Rolling occurs whenever a new time frame of the selected kind is started, meaning for minutely rolling a new queue is created when a new minute begins and daily rolls occurs at midnight UTC time. -This behavior can be changed using Chronicle Queue’s attribute `epoch()`. +This behavior can be changed using Chronicle Queue's attribute `epoch()`. Epoch refers to a milliseconds offset to the set time frame. In other words, if you set the epoch to be `epoch(1)` and use `RollCycles.DAILY` the queue will roll at 1 millisecond past midnight UTC time. Putting it together: @@ -146,7 +146,7 @@ For example, you may have up to 1 trillion messages per day and you need 23-bit With hourly and daily-rolling it's pretty limitless. Also, by changing the `epoch`, you can extend the dates further, shifting the limit between the first and last cycle to 31-bits or 23-bits. -== Timezone Rollover ★ +== Timezone Rollover (Enterprise) Chronicle Queue bases its roll times on the UTC time zone. However, Chronicle Queue Enterprise supports time zone rollover. @@ -183,7 +183,7 @@ SingleChronicleQueue queue = ChronicleQueue.singleBuilder("/timezone") .build(); ---- -== Archiving Old Queue Files ★ +== Archiving Old Queue Files (Enterprise) Over time, it may become necessary to automatically delete or archive old queue files. An automated process needs to ensure that there are no active file-handles open on a queue file before attempting to delete. facilitate housekeeping of older roll files, Chronicle Queue Enterprise provides the xref:command-line:command_line_tools.adoc#_archiverollfiles[ArchiveRollFiles utility]. @@ -199,7 +199,7 @@ However, it is still recommended avoiding rolling at critical points in time to In systems that are not always active, it is advised to schedule rolls during the down-time. However, for applications with a busy feed and no down-time, Chronicle recommends using minutely rolling (a new queue is created every minute). -This keeps jitter to a minimum, as only one minute’s worth of data has to be unmapped on a queue-roll. +This keeps jitter to a minimum, as only one minute's worth of data has to be unmapped on a queue-roll. === Avoid Large Files diff --git a/docs/antora/modules/encryption/nav.adoc b/docs/antora/modules/encryption/nav.adoc index feee1ab5c9..bcdf28de08 100644 --- a/docs/antora/modules/encryption/nav.adoc +++ b/docs/antora/modules/encryption/nav.adoc @@ -1 +1 @@ -* xref:encryption.adoc[Encryption ★] +* xref:encryption.adoc[Encryption (Enterprise)] diff --git a/docs/antora/modules/introduction/pages/introduction.adoc b/docs/antora/modules/introduction/pages/introduction.adoc index 81cc7daef5..49fbda50b2 100644 --- a/docs/antora/modules/introduction/pages/introduction.adoc +++ b/docs/antora/modules/introduction/pages/introduction.adoc @@ -33,10 +33,10 @@ Tailing :: Learn how to read from a queue. Performance tuning :: As every environment is different, this chapter points out key factors to obtain the best performance with Chronicle Queue. -★ Replication :: +Replication (Enterprise) :: Replication is an Enterprise feature allowing queues to be automatically replicated across machines or cores. This chapter teaches how that is accomplished. -★ Encryption :: +Encryption (Enterprise) :: Learn how to protect the contents of a Chronicle Queue with encryption. === Runnable Demos @@ -60,7 +60,7 @@ You can find our demos link:https://github.com/OpenHFT/Chronicle-Queue-Demo/tree Chronicle Queue Enterprise is a commercially supported version of Chronicle Queue OSS. This documentation covers the open source functionality but is also extended by a few chapters describing enterprise specific features. -These sections are clearly marked with a star-symbol (★) and include: +These sections are clearly marked with an Enterprise marker and include: * Encryption of message queues and messages. diff --git a/docs/antora/modules/performance/nav.adoc b/docs/antora/modules/performance/nav.adoc index d708bb5862..50af6889f1 100644 --- a/docs/antora/modules/performance/nav.adoc +++ b/docs/antora/modules/performance/nav.adoc @@ -1,4 +1,4 @@ * xref:performance.adoc[Performance Tuning] ** xref:performance-test-results.adoc[Performance Test Results] -** xref:pretouching.adoc[Pre-Touching ★] +** xref:pretouching.adoc[Pre-Touching (Enterprise)] ** xref:contended-writes.adoc[Contended Writes] diff --git a/docs/antora/modules/performance/pages/performance.adoc b/docs/antora/modules/performance/pages/performance.adoc index e81f58a2b0..bf5fa5cbe6 100644 --- a/docs/antora/modules/performance/pages/performance.adoc +++ b/docs/antora/modules/performance/pages/performance.adoc @@ -10,7 +10,7 @@ author: Julia Gustafsson On most systems `System.nanoTime()` is roughly the number of nanoseconds since the system last rebooted (although different JVMs may behave differently). This is the same across JVMs on the same machine, but wildly different between machines. The absolute difference when it comes to machines is meaningless. -However, the information can be used to detect outliers; you can’t determine what the best latency is, but you can determine how far off the best latencies you are. +However, the information can be used to detect outliers; you can't determine what the best latency is, but you can determine how far off the best latencies you are. This is useful if you are focusing on the 99th percentile latencies. We have a class called `RunningMinimum` to obtain timings from different machines, while compensating for a drift in the nanoTime between machines. The more often you take measurements, the more accurate this running minimum is. diff --git a/docs/antora/modules/performance/pages/pretouching.adoc b/docs/antora/modules/performance/pages/pretouching.adoc index 3a238973a0..c2216e6f83 100644 --- a/docs/antora/modules/performance/pages/pretouching.adoc +++ b/docs/antora/modules/performance/pages/pretouching.adoc @@ -18,7 +18,7 @@ There are two ways to perform pre-touching, outlined below: Triggering pre-touches manually :: Perform pre-touching once for the current appender using `ExcerptAppender.pretouch()` -Automatically pre-touching using Chronicle's preloader ★ :: +Automatically pre-touching using Chronicle's preloader (Enterprise) :: The preloader automatically pre-touches segments at a given time interval _(Enterprise feature)_. == Pre-Touching Once @@ -50,7 +50,7 @@ newSingleThreadScheduledExecutor().scheduleAtFixedRate( <3> The period between successive executions <4> The time unit of the `initialDelay` and period parameters -== Automatic Pre-Touching ★ +== Automatic Pre-Touching (Enterprise) The easiest way to configure periodical pre-touching is by using Chronicle's out-of-process preloader. It can be activated in the Chronicle Queue Builder as follows: diff --git a/docs/antora/modules/queue-operations/pages/appending.adoc b/docs/antora/modules/queue-operations/pages/appending.adoc index d1816f92c2..20b34d7166 100644 --- a/docs/antora/modules/queue-operations/pages/appending.adoc +++ b/docs/antora/modules/queue-operations/pages/appending.adoc @@ -46,7 +46,7 @@ As shown above, the document context can be used to append data directly: [source,java] ---- try (final DocumentContext dc = appender.writingDocument()) { - dc.wire().write().text(“Your text data“); + dc.wire().write().text("Your text data"); } ---- @@ -55,7 +55,7 @@ The length of the data is written to the header when the try-with-resources bloc [source,java] ---- try (final DocumentContext dc = appender.writingDocument()) { - dc.wire().write().text(“your text data“); + dc.wire().write().text("your text data"); System.out.println("your data was store to index="+ dc.index()); } ---- diff --git a/docs/antora/modules/queue-operations/pages/indexing.adoc b/docs/antora/modules/queue-operations/pages/indexing.adoc index 9c1f311b27..d29ecb9e3f 100644 --- a/docs/antora/modules/queue-operations/pages/indexing.adoc +++ b/docs/antora/modules/queue-operations/pages/indexing.adoc @@ -56,7 +56,7 @@ queue.lastIndex() This will return the index of the last data present in the queue (or -1 for an empty queue). -WARNING: If the queue is being written to concurrently it’s possible the value may be an under-estimate, as subsequent entries may have been written even before it was returned. +WARNING: If the queue is being written to concurrently it's possible the value may be an under-estimate, as subsequent entries may have been written even before it was returned. [#_tail_from_index] == Tail from Index diff --git a/docs/antora/modules/queue-operations/pages/tailing.adoc b/docs/antora/modules/queue-operations/pages/tailing.adoc index dbf68a47e8..f86ccf6481 100644 --- a/docs/antora/modules/queue-operations/pages/tailing.adoc +++ b/docs/antora/modules/queue-operations/pages/tailing.adoc @@ -200,14 +200,7 @@ Chronicle Queue stores its data in binary format, with a file extension of `cq4` [source,text] ---- -\�@πheader∂SCQStoreÇE��»wireType∂WireTypeÊBINARYÕwritePositionèèèèß��������ƒroll∂SCQSRollÇ*���∆length¶ÄÓ6�∆format -ÎyyyyMMdd-HH≈epoch¶ÄÓ6�»indexing∂ SCQSIndexingÇN��� indexCount•�ÃindexSpacingÀindex2Indexé����ß��������…lastIndexé� -���ß��������fllastAcknowledgedIndexReplicatedé�����ߡˇˇˇˇˇˇˇ»recovery∂TimedStoreRecoveryÇ���…timeStampèèèß -��������������������������������������������������������������������������������������������� -��������������������������������������������������������������������������������������������� -��������������������������������������������������������������������������������������������� -��������������������������������������������������������������������������������������������� -����������������������������������������������������������������� +[binary cq4 bytes omitted] ---- This can often be a bit difficult to read, so it is better to dump the `cq4` files as text. diff --git a/docs/antora/modules/replication/nav.adoc b/docs/antora/modules/replication/nav.adoc index c658c0467a..aa7f25eda3 100644 --- a/docs/antora/modules/replication/nav.adoc +++ b/docs/antora/modules/replication/nav.adoc @@ -1 +1 @@ -* xref:replication.adoc[Queue Replication ★] +* xref:replication.adoc[Queue Replication (Enterprise)] diff --git a/docs/antora/modules/tailing/pages/tailing.adoc b/docs/antora/modules/tailing/pages/tailing.adoc index 04a00fbba0..9cf2818a4d 100644 --- a/docs/antora/modules/tailing/pages/tailing.adoc +++ b/docs/antora/modules/tailing/pages/tailing.adoc @@ -200,14 +200,7 @@ Chronicle Queue stores its data in binary format, with a file extension of `cq4` [source,text] ---- -\�@πheader∂SCQStoreÇE��»wireType∂WireTypeÊBINARYÕwritePositionèèèèß��������ƒroll∂SCQSRollÇ*���∆length¶ÄÓ6�∆format -ÎyyyyMMdd-HH≈epoch¶ÄÓ6�»indexing∂ SCQSIndexingÇN��� indexCount•�ÃindexSpacingÀindex2Indexé����ß��������…lastIndexé� -���ß��������fllastAcknowledgedIndexReplicatedé�����ߡˇˇˇˇˇˇˇ»recovery∂TimedStoreRecoveryÇ���…timeStampèèèß -��������������������������������������������������������������������������������������������� -��������������������������������������������������������������������������������������������� -��������������������������������������������������������������������������������������������� -��������������������������������������������������������������������������������������������� -����������������������������������������������������������������� +[binary cq4 bytes omitted] ---- This can often be a bit difficult to read, so it is better to dump the `cq4` files as text. diff --git a/src/main/java/net/openhft/chronicle/queue/ExcerptTailer.java b/src/main/java/net/openhft/chronicle/queue/ExcerptTailer.java index 574053ee8f..ab6b63320e 100644 --- a/src/main/java/net/openhft/chronicle/queue/ExcerptTailer.java +++ b/src/main/java/net/openhft/chronicle/queue/ExcerptTailer.java @@ -122,7 +122,7 @@ default long lastReadIndex() { * queue.lastCycle() is cached, as finding the last cycle is expensive, it requires asking the * directory for the Files.list() so, this cache is only refreshed if the call toEnd() is in a * new millisecond. Hence, a whole milliseconds with of data could be added to the - * chronicle-queue that toEnd() won’t see. For appenders that are using the same queue + * chronicle-queue that toEnd() won't see. For appenders that are using the same queue * instance ( and with then same JVM ), they can be informed that the last cycle has * changed, this will yield better results, but atomicity can still not be guaranteed. * diff --git a/src/main/java/net/openhft/chronicle/queue/impl/WireStoreFactory.java b/src/main/java/net/openhft/chronicle/queue/impl/WireStoreFactory.java index 9b16b2c57f..1cf728053c 100644 --- a/src/main/java/net/openhft/chronicle/queue/impl/WireStoreFactory.java +++ b/src/main/java/net/openhft/chronicle/queue/impl/WireStoreFactory.java @@ -13,8 +13,8 @@ * {@link SingleChronicleQueueStore}. It functions as a factory to produce queue stores, * utilizing a rolling chronicle queue and a wire to initialize each store. * - * This interface extends {@link BiFunction}, meaning it takes two arguments — a - * {@link RollingChronicleQueue} and a {@link Wire} — and returns a {@link SingleChronicleQueueStore}. + * This interface extends {@link BiFunction}, taking a {@link RollingChronicleQueue} + * and a {@link Wire} and returning a {@link SingleChronicleQueueStore}. */ @FunctionalInterface public interface WireStoreFactory extends BiFunction { From 0d8ce018d89f601332e238c0f289f4181af451f0 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Tue, 2 Jun 2026 11:04:31 +0100 Subject: [PATCH 2/7] test: add shared queue cleanup drain helpers --- .../chronicle/queue/QueueTestCommon.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/test/java/net/openhft/chronicle/queue/QueueTestCommon.java b/src/test/java/net/openhft/chronicle/queue/QueueTestCommon.java index ba0f4d5c29..ab19619cdb 100644 --- a/src/test/java/net/openhft/chronicle/queue/QueueTestCommon.java +++ b/src/test/java/net/openhft/chronicle/queue/QueueTestCommon.java @@ -7,6 +7,7 @@ import net.openhft.chronicle.core.OS; import net.openhft.chronicle.core.io.AbstractCloseable; import net.openhft.chronicle.core.io.AbstractReferenceCounted; +import net.openhft.chronicle.core.io.BackgroundResourceReleaser; import net.openhft.chronicle.core.io.IOTools; import net.openhft.chronicle.core.onoes.ExceptionKey; import net.openhft.chronicle.core.onoes.LogLevel; @@ -14,6 +15,7 @@ import net.openhft.chronicle.core.threads.ThreadDump; import net.openhft.chronicle.core.time.SystemTimeProvider; import net.openhft.chronicle.queue.util.HugetlbfsTestUtil; +import net.openhft.chronicle.testframework.GcControls; import net.openhft.chronicle.testframework.exception.ExceptionTracker; import net.openhft.chronicle.wire.MessageHistory; import org.jetbrains.annotations.NotNull; @@ -213,6 +215,8 @@ public void deleteTargetDirTestArtifacts() { .map(File::getName) .collect(Collectors.toSet()); + drainBackgroundCleanup(); + currentFilesInTarget.stream() .filter(fileName -> !targetAllowList.contains(fileName)) .forEach(fileName -> { @@ -246,7 +250,35 @@ public void afterChecks() { protected void preAfter() { } + protected static void drainBackgroundCleanup() { + BackgroundResourceReleaser.releasePendingResources(); + try { + GcControls.waitForGcCycle(); + BackgroundResourceReleaser.releasePendingResources(); + } catch (IllegalStateException ignored) { + // Explicit GC can be disabled by the test JVM; cleanup should remain best-effort. + } + } + + /** + * Delete a test directory after draining background cleanup. Tolerates failures on Windows, + * where mapped files can briefly hold handles open after release. + */ + protected static void deleteDirAfterCleanup(File dir) { + drainBackgroundCleanup(); + try { + IOTools.deleteDirWithFiles(dir, 2); + } catch (Exception e) { + if (OS.isWindows()) { + Jvm.debug().on(QueueTestCommon.class, "Ignoring cleanup failure on Windows for " + dir, e); + return; + } + throw e; + } + } + protected void tearDown() { + drainBackgroundCleanup(); // should be able to remove tmp dirs tmpDirs.forEach(file -> { if (file.exists() && !IOTools.deleteDirWithFiles(file)) { From f218230ae16a4bd6cbba500a8bd5edcec49d02cd Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Tue, 2 Jun 2026 11:05:29 +0100 Subject: [PATCH 3/7] test: drain background cleanup before file removal --- .../chronicle/queue/AcquireReleaseTest.java | 9 ++++--- .../queue/ChronicleRollingIssueTest.java | 8 ++---- .../chronicle/queue/CreateAtIndexTest.java | 7 +---- .../chronicle/queue/MarshallableTest.java | 8 +----- .../openhft/chronicle/queue/OvertakeTest.java | 8 +++--- ...QueueAppendAfterRollReplayedIssueTest.java | 10 ++----- .../chronicle/queue/ReadWriteTest.java | 3 +-- .../chronicle/queue/TestDeleteQueueFile.java | 3 ++- .../chronicle/queue/WriteBytesTest.java | 26 +++---------------- .../chronicle/queue/WriteReadTextTest.java | 1 + .../queue/cleanup/OnReleaseTest.java | 5 ++-- .../queue/impl/single/EmptyRollCycleTest.java | 3 ++- .../impl/single/MetadataDeletionTests.java | 4 +-- .../queue/impl/single/NormaliseEOFsTest.java | 2 +- .../queue/impl/single/NotCompleteTest.java | 9 ++----- .../queue/impl/single/QueueEpochTest.java | 3 +-- .../queue/impl/single/RollingCycleTest.java | 8 ++---- .../SingleChronicleQueueBuilderTest.java | 1 + .../internal/reader/ChronicleReaderTest.java | 2 +- .../chronicle/queue/issue/DeleteFileTest.java | 9 ++++--- 20 files changed, 42 insertions(+), 87 deletions(-) diff --git a/src/test/java/net/openhft/chronicle/queue/AcquireReleaseTest.java b/src/test/java/net/openhft/chronicle/queue/AcquireReleaseTest.java index 8ea3e9fad1..fe80d5ddb1 100644 --- a/src/test/java/net/openhft/chronicle/queue/AcquireReleaseTest.java +++ b/src/test/java/net/openhft/chronicle/queue/AcquireReleaseTest.java @@ -4,7 +4,6 @@ package net.openhft.chronicle.queue; import net.openhft.chronicle.core.annotation.RequiredForClient; -import net.openhft.chronicle.core.io.BackgroundResourceReleaser; import net.openhft.chronicle.core.io.IOTools; import net.openhft.chronicle.core.time.SetTimeProvider; import net.openhft.chronicle.core.time.TimeProvider; @@ -64,7 +63,7 @@ public void onReleased(int cycle, File file) { } } - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); Assert.assertEquals(iter, acount.get()); Assert.assertEquals(iter, qcount.get()); @@ -122,7 +121,8 @@ public void onReleased(int cycle, File file) { // new appender created final ExcerptAppender appender = queue.createAppender()) { appender.writeText("Main thread: Hello world"); - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); + assertEquals(1, acount.get()); stp.advanceMillis(1000L); // advance 1 cycle, so that cleanupStoreFilesWithNoData() acquires store @@ -134,7 +134,8 @@ public void onReleased(int cycle, File file) { // Do nothing with it } } - BackgroundResourceReleaser.releasePendingResources(); + + drainBackgroundCleanup(); assertEquals(2, acount.get()); diff --git a/src/test/java/net/openhft/chronicle/queue/ChronicleRollingIssueTest.java b/src/test/java/net/openhft/chronicle/queue/ChronicleRollingIssueTest.java index 35d49d73b9..a4476a4dc3 100644 --- a/src/test/java/net/openhft/chronicle/queue/ChronicleRollingIssueTest.java +++ b/src/test/java/net/openhft/chronicle/queue/ChronicleRollingIssueTest.java @@ -6,7 +6,6 @@ import net.openhft.chronicle.core.Jvm; import net.openhft.chronicle.core.OS; import net.openhft.chronicle.core.annotation.RequiredForClient; -import net.openhft.chronicle.core.io.IORuntimeException; import net.openhft.chronicle.core.io.IOTools; import net.openhft.chronicle.core.util.Time; import net.openhft.chronicle.queue.impl.StoreFileListener; @@ -14,6 +13,7 @@ import org.junit.Before; import org.junit.Test; +import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -108,11 +108,7 @@ public void test() throws InterruptedException { thread.interrupt(); thread.join(1000); } - try { - IOTools.deleteDirWithFiles(path, 2); - } catch (IORuntimeException e) { - Jvm.debug().on(ChronicleRollingIssueTest.class, "Failed to clean up test directory", e); - } + deleteDirAfterCleanup(new File(path)); } } } diff --git a/src/test/java/net/openhft/chronicle/queue/CreateAtIndexTest.java b/src/test/java/net/openhft/chronicle/queue/CreateAtIndexTest.java index 051d7239d1..a430959351 100644 --- a/src/test/java/net/openhft/chronicle/queue/CreateAtIndexTest.java +++ b/src/test/java/net/openhft/chronicle/queue/CreateAtIndexTest.java @@ -5,8 +5,6 @@ import net.openhft.chronicle.bytes.Bytes; import net.openhft.chronicle.core.annotation.RequiredForClient; -import net.openhft.chronicle.core.io.IORuntimeException; -import net.openhft.chronicle.core.io.IOTools; import net.openhft.chronicle.queue.impl.single.IllegalIndexException; import net.openhft.chronicle.queue.impl.single.InternalAppender; import net.openhft.chronicle.wire.DocumentContext; @@ -86,10 +84,7 @@ public class CreateAtIndexTest extends QueueTestCommon { appender.writeBytes(0x421d00000003L, HELLO_WORLD); } - try { - IOTools.deleteDirWithFiles(tmp, 2); - } catch (IORuntimeException ignored) { - } + deleteDirAfterCleanup(tmp); } @Test diff --git a/src/test/java/net/openhft/chronicle/queue/MarshallableTest.java b/src/test/java/net/openhft/chronicle/queue/MarshallableTest.java index 88485473bf..f96392bfc4 100644 --- a/src/test/java/net/openhft/chronicle/queue/MarshallableTest.java +++ b/src/test/java/net/openhft/chronicle/queue/MarshallableTest.java @@ -3,8 +3,6 @@ */ package net.openhft.chronicle.queue; -import net.openhft.chronicle.core.io.IORuntimeException; -import net.openhft.chronicle.core.io.IOTools; import org.junit.Test; import java.io.File; @@ -36,11 +34,7 @@ public void testWriteText() { assertEquals("" + i, sb.toString()); } } finally { - try { - IOTools.deleteDirWithFiles(dir, 2); - } catch (IORuntimeException e) { - // ignored - } + deleteDirAfterCleanup(dir); } } } diff --git a/src/test/java/net/openhft/chronicle/queue/OvertakeTest.java b/src/test/java/net/openhft/chronicle/queue/OvertakeTest.java index dea8b89536..49865cb519 100644 --- a/src/test/java/net/openhft/chronicle/queue/OvertakeTest.java +++ b/src/test/java/net/openhft/chronicle/queue/OvertakeTest.java @@ -6,8 +6,9 @@ import net.openhft.chronicle.core.OS; import net.openhft.chronicle.core.annotation.RequiredForClient; import net.openhft.chronicle.core.io.Closeable; -import net.openhft.chronicle.core.io.IOTools; import net.openhft.chronicle.core.util.Time; + +import java.io.File; import net.openhft.chronicle.threads.NamedThreadFactory; import net.openhft.chronicle.wire.DocumentContext; import org.jetbrains.annotations.NotNull; @@ -100,10 +101,7 @@ public void appendAndTail() { @Override public void tearDown() { - try { - IOTools.deleteDirWithFiles(path, 2); - } catch (Exception ignored) { - } + deleteDirAfterCleanup(new File(path)); } @Test diff --git a/src/test/java/net/openhft/chronicle/queue/QueueAppendAfterRollReplayedIssueTest.java b/src/test/java/net/openhft/chronicle/queue/QueueAppendAfterRollReplayedIssueTest.java index 14a98dc305..3357d7b63d 100644 --- a/src/test/java/net/openhft/chronicle/queue/QueueAppendAfterRollReplayedIssueTest.java +++ b/src/test/java/net/openhft/chronicle/queue/QueueAppendAfterRollReplayedIssueTest.java @@ -3,15 +3,13 @@ */ package net.openhft.chronicle.queue; -import net.openhft.chronicle.core.Jvm; import net.openhft.chronicle.core.OS; -import net.openhft.chronicle.core.io.IORuntimeException; -import net.openhft.chronicle.core.io.IOTools; import net.openhft.chronicle.core.time.SetTimeProvider; import net.openhft.chronicle.core.util.Time; import net.openhft.chronicle.wire.DocumentContext; import org.junit.Test; +import java.io.File; import java.util.HashMap; import java.util.Map; @@ -58,11 +56,7 @@ public void test() { } } finally { - try { - IOTools.deleteDirWithFiles(path, 2); - } catch (IORuntimeException e) { - Jvm.debug().on(QueueAppendAfterRollReplayedIssueTest.class, "Failed to delete test path", e); - } + deleteDirAfterCleanup(new File(path)); } } } diff --git a/src/test/java/net/openhft/chronicle/queue/ReadWriteTest.java b/src/test/java/net/openhft/chronicle/queue/ReadWriteTest.java index 46ba77565e..28313eae7a 100644 --- a/src/test/java/net/openhft/chronicle/queue/ReadWriteTest.java +++ b/src/test/java/net/openhft/chronicle/queue/ReadWriteTest.java @@ -6,7 +6,6 @@ import net.openhft.chronicle.core.Jvm; import net.openhft.chronicle.core.OS; import net.openhft.chronicle.core.annotation.RequiredForClient; -import net.openhft.chronicle.core.io.BackgroundResourceReleaser; import net.openhft.chronicle.queue.impl.single.SingleChronicleQueueBuilder; import net.openhft.chronicle.wire.DocumentContext; import org.junit.After; @@ -50,7 +49,7 @@ public void setup() { */ @After public void forceCleanupToDeFlakeTests() { - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); } @Override diff --git a/src/test/java/net/openhft/chronicle/queue/TestDeleteQueueFile.java b/src/test/java/net/openhft/chronicle/queue/TestDeleteQueueFile.java index c76511bfc9..535b97a81d 100644 --- a/src/test/java/net/openhft/chronicle/queue/TestDeleteQueueFile.java +++ b/src/test/java/net/openhft/chronicle/queue/TestDeleteQueueFile.java @@ -528,7 +528,8 @@ private QueueWithCycleDetails createQueueWithNRollCycles(int numberOfCycles, Con long firstIndexInCycle = writeTextAndReturnFirstIndex(appender, "test" + (i + 1)); long lastIndexInCycle = appender.lastIndexAppended(); timeProvider.advanceMillis(TimeUnit.DAYS.toMillis(1)); - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); + return new RollCycleDetails( queue.rollCycle().toCycle(firstIndexInCycle), firstIndexInCycle, diff --git a/src/test/java/net/openhft/chronicle/queue/WriteBytesTest.java b/src/test/java/net/openhft/chronicle/queue/WriteBytesTest.java index e27107b163..809b54a9d1 100644 --- a/src/test/java/net/openhft/chronicle/queue/WriteBytesTest.java +++ b/src/test/java/net/openhft/chronicle/queue/WriteBytesTest.java @@ -7,8 +7,6 @@ import net.openhft.chronicle.bytes.PageUtil; import net.openhft.chronicle.core.OS; import net.openhft.chronicle.core.annotation.RequiredForClient; -import net.openhft.chronicle.core.io.IORuntimeException; -import net.openhft.chronicle.core.io.IOTools; import net.openhft.chronicle.core.time.SetTimeProvider; import net.openhft.chronicle.queue.impl.single.SingleChronicleQueueBuilder; import net.openhft.chronicle.wire.DocumentContext; @@ -57,11 +55,7 @@ public void testWriteBytes() { // System.out.println(new String(incomingMsgBytes)); } finally { - try { - IOTools.deleteDirWithFiles(dir, 2); - } catch (IORuntimeException e) { - // ignored - } + deleteDirAfterCleanup(dir); } } @@ -1044,11 +1038,7 @@ public void testWriteBytesAndDump() { "# 126928 bytes remaining\n", queue.dump()); } finally { - try { - IOTools.deleteDirWithFiles(dir, 2); - } catch (IORuntimeException e) { - // ignored - } + deleteDirAfterCleanup(dir); } } @@ -1079,11 +1069,7 @@ public void testWriteBytesWithDirectBufferReuse() { readBuffer.releaseLast(); } } finally { - try { - IOTools.deleteDirWithFiles(dir, 2); - } catch (IORuntimeException e) { - // ignored - } + deleteDirAfterCleanup(dir); } } @@ -1118,11 +1104,7 @@ public void testRollCycleCapacityConsistency() { assertEquals("Wire buffer capacity should remain stable across rolls", initialCapacity, postRollCapacity); assertNextUtf8(tailer, "cycle-2"); } finally { - try { - IOTools.deleteDirWithFiles(dir, 2); - } catch (IORuntimeException e) { - // ignored - } + deleteDirAfterCleanup(dir); } } diff --git a/src/test/java/net/openhft/chronicle/queue/WriteReadTextTest.java b/src/test/java/net/openhft/chronicle/queue/WriteReadTextTest.java index d4f673eae1..1be14dd7c7 100644 --- a/src/test/java/net/openhft/chronicle/queue/WriteReadTextTest.java +++ b/src/test/java/net/openhft/chronicle/queue/WriteReadTextTest.java @@ -187,6 +187,7 @@ private void doTest(@NotNull String... problematic) { } } } + drainBackgroundCleanup(); IOTools.deleteDirWithFiles(myPath); } } diff --git a/src/test/java/net/openhft/chronicle/queue/cleanup/OnReleaseTest.java b/src/test/java/net/openhft/chronicle/queue/cleanup/OnReleaseTest.java index 1d750556a1..b3421fdd54 100644 --- a/src/test/java/net/openhft/chronicle/queue/cleanup/OnReleaseTest.java +++ b/src/test/java/net/openhft/chronicle/queue/cleanup/OnReleaseTest.java @@ -5,7 +5,6 @@ import net.openhft.chronicle.bytes.PageUtil; import net.openhft.chronicle.core.OS; -import net.openhft.chronicle.core.io.BackgroundResourceReleaser; import net.openhft.chronicle.core.io.IOTools; import net.openhft.chronicle.core.time.SetTimeProvider; import net.openhft.chronicle.core.util.Time; @@ -62,12 +61,14 @@ private void onRelease0() { for (int i = 0; i < messageCount; i++) { appender.writeText("hello-" + i); assertNotNull(tailer.readText()); - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); + assertEquals(i, writeRoll.get()); assertEquals(i, readRoll.get()); stp.advanceMillis(66_000); } } finally { + drainBackgroundCleanup(); IOTools.deleteDirWithFiles(path); } } diff --git a/src/test/java/net/openhft/chronicle/queue/impl/single/EmptyRollCycleTest.java b/src/test/java/net/openhft/chronicle/queue/impl/single/EmptyRollCycleTest.java index 11d41b9165..36ed3d1b39 100644 --- a/src/test/java/net/openhft/chronicle/queue/impl/single/EmptyRollCycleTest.java +++ b/src/test/java/net/openhft/chronicle/queue/impl/single/EmptyRollCycleTest.java @@ -143,7 +143,8 @@ private void createQueueWithEmptyRollCycleAtEnd() throws IOException { final Path lastRollCycle = dataDirectory.resolve(EMPTY_ROLL_CYCLE_NAME); // This test was flaky some small percentage of time due to some lingering file handles. // Ensure everything is fully cleaned up before deleting the file. - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); + Files.delete(lastRollCycle); // Replace it with an empty file diff --git a/src/test/java/net/openhft/chronicle/queue/impl/single/MetadataDeletionTests.java b/src/test/java/net/openhft/chronicle/queue/impl/single/MetadataDeletionTests.java index 26471ec0fd..a0e02d36f1 100644 --- a/src/test/java/net/openhft/chronicle/queue/impl/single/MetadataDeletionTests.java +++ b/src/test/java/net/openhft/chronicle/queue/impl/single/MetadataDeletionTests.java @@ -35,7 +35,7 @@ void singleCycleFile() { appender.writeText("hello world"); } finally { // Force release of resources to ensure that they are truly released by the time we clean up metadata - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); } // Imagine that system has shut down, delete metadata @@ -76,7 +76,7 @@ void multipleCycleFiles() { appender.writeText("4"); } finally { // Force release of resources to ensure that they are truly released by the time we clean up metadata - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); } // Imagine that system has shut down, delete metadata diff --git a/src/test/java/net/openhft/chronicle/queue/impl/single/NormaliseEOFsTest.java b/src/test/java/net/openhft/chronicle/queue/impl/single/NormaliseEOFsTest.java index 94a4e8876b..73848d556d 100644 --- a/src/test/java/net/openhft/chronicle/queue/impl/single/NormaliseEOFsTest.java +++ b/src/test/java/net/openhft/chronicle/queue/impl/single/NormaliseEOFsTest.java @@ -64,7 +64,7 @@ public void clearLogLevelProperty() { @After public void cleanupQueueData() { - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); IOTools.deleteDirWithFilesOrThrow(QUEUE_PATH); } diff --git a/src/test/java/net/openhft/chronicle/queue/impl/single/NotCompleteTest.java b/src/test/java/net/openhft/chronicle/queue/impl/single/NotCompleteTest.java index a59d07e75f..d32aab2e23 100644 --- a/src/test/java/net/openhft/chronicle/queue/impl/single/NotCompleteTest.java +++ b/src/test/java/net/openhft/chronicle/queue/impl/single/NotCompleteTest.java @@ -130,13 +130,8 @@ public void testInterruptOrExceptionDuringSerialisation() throws InterruptedExce assertFalse(reader.readOne()); } } finally { - try { - IOTools.deleteDirWithFiles(tmpDir, 2); - } catch (Exception e) { - if (e instanceof AccessDeniedException && OS.isWindows()) - return; - throw e; - } + drainBackgroundCleanup(); + IOTools.deleteDirWithFiles(tmpDir, 2); } } diff --git a/src/test/java/net/openhft/chronicle/queue/impl/single/QueueEpochTest.java b/src/test/java/net/openhft/chronicle/queue/impl/single/QueueEpochTest.java index f552406f0d..32712839f8 100644 --- a/src/test/java/net/openhft/chronicle/queue/impl/single/QueueEpochTest.java +++ b/src/test/java/net/openhft/chronicle/queue/impl/single/QueueEpochTest.java @@ -3,7 +3,6 @@ */ package net.openhft.chronicle.queue.impl.single; -import net.openhft.chronicle.core.io.BackgroundResourceReleaser; import net.openhft.chronicle.queue.ChronicleQueue; import net.openhft.chronicle.queue.ExcerptAppender; import net.openhft.chronicle.queue.QueueTestCommon; @@ -118,7 +117,7 @@ public void onReleased(final int cycle, final File file) { } int numberOfRollEvents() { - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); return numberOfRollEvents; } diff --git a/src/test/java/net/openhft/chronicle/queue/impl/single/RollingCycleTest.java b/src/test/java/net/openhft/chronicle/queue/impl/single/RollingCycleTest.java index ffc55cf62f..3e7be59160 100644 --- a/src/test/java/net/openhft/chronicle/queue/impl/single/RollingCycleTest.java +++ b/src/test/java/net/openhft/chronicle/queue/impl/single/RollingCycleTest.java @@ -6,7 +6,6 @@ import net.openhft.chronicle.bytes.*; import net.openhft.chronicle.core.OS; import net.openhft.chronicle.core.io.IORuntimeException; -import net.openhft.chronicle.core.io.IOTools; import net.openhft.chronicle.core.time.SetTimeProvider; import net.openhft.chronicle.core.util.Time; import net.openhft.chronicle.queue.ChronicleQueue; @@ -20,6 +19,7 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import java.io.File; import java.util.Arrays; import java.util.Collection; import java.util.Random; @@ -284,11 +284,7 @@ public void testRollCycle() { expected = expected.replaceAll("\\n\\d+ bytes remaining", "\n4 bytes remaining"); assertEquals(expected, dump); - try { - IOTools.deleteDirWithFiles(basePath, 2); - } catch (IORuntimeException e) { - e.printStackTrace(); - } + deleteDirAfterCleanup(new File(basePath)); } } diff --git a/src/test/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueueBuilderTest.java b/src/test/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueueBuilderTest.java index a1eb435c4c..d76fe3a0e9 100644 --- a/src/test/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueueBuilderTest.java +++ b/src/test/java/net/openhft/chronicle/queue/impl/single/SingleChronicleQueueBuilderTest.java @@ -248,6 +248,7 @@ public void builderAppliesCoreOverridesForLoggerConfigs() { assertEquals(expected, tailer.readText()); } } finally { + drainBackgroundCleanup(); IOTools.deleteDirWithFiles(tmpDir); } } diff --git a/src/test/java/net/openhft/chronicle/queue/internal/reader/ChronicleReaderTest.java b/src/test/java/net/openhft/chronicle/queue/internal/reader/ChronicleReaderTest.java index 64d5f31e60..6c118d1884 100644 --- a/src/test/java/net/openhft/chronicle/queue/internal/reader/ChronicleReaderTest.java +++ b/src/test/java/net/openhft/chronicle/queue/internal/reader/ChronicleReaderTest.java @@ -931,7 +931,7 @@ public void findByBinarySearchWithDeletedRollCyles() { } } // Just make sure Windows has closed all the files before we try to delete - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); // delete the 4th roll cycle assertTrue("Couldn't delete cycle, test is broken", queueDir.toPath().resolve("19700101-000009T.cq4").toFile().delete()); diff --git a/src/test/java/net/openhft/chronicle/queue/issue/DeleteFileTest.java b/src/test/java/net/openhft/chronicle/queue/issue/DeleteFileTest.java index e02dc9e117..ad090db7f9 100644 --- a/src/test/java/net/openhft/chronicle/queue/issue/DeleteFileTest.java +++ b/src/test/java/net/openhft/chronicle/queue/issue/DeleteFileTest.java @@ -4,11 +4,11 @@ package net.openhft.chronicle.queue.issue; import net.openhft.chronicle.core.OS; -import net.openhft.chronicle.core.io.BackgroundResourceReleaser; import net.openhft.chronicle.core.io.IOTools; import net.openhft.chronicle.queue.ChronicleQueue; import net.openhft.chronicle.queue.ExcerptAppender; import net.openhft.chronicle.queue.ExcerptTailer; +import net.openhft.chronicle.queue.QueueTestCommon; import net.openhft.chronicle.queue.impl.single.SingleChronicleQueue; import net.openhft.chronicle.queue.impl.single.SingleChronicleQueueBuilder; import net.openhft.chronicle.queue.rollcycles.TestRollCycles; @@ -20,7 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class DeleteFileTest { +public class DeleteFileTest extends QueueTestCommon { @Test public void testMain() { final long[] clock = {1730366325_000L}; @@ -48,7 +48,8 @@ public void testMain() { // can't delete while in use on windows, so have to close. if (!OS.isLinux()) tailer0.close(); - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); + assertTrue(firstCycleFile.delete()); // sanity after deletion: continue reading across roll cycle @@ -82,7 +83,7 @@ public void testMain() { assertEquals("2 2 2 2 2", twoA2 + " " + twoA + " " + two0 + " " + twoB + " " + twoC); } } finally { - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); IOTools.deleteDirWithFilesOrThrow("queue"); } } From c6bf6428dc52d8ed72c9d3ad79f3acc59f21d40f Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Tue, 2 Jun 2026 11:06:54 +0100 Subject: [PATCH 4/7] test: stabilise appender file handle leak checks --- .../single/AppenderFileHandleLeakTest.java | 65 ++++++++++++------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/src/test/java/net/openhft/chronicle/queue/impl/single/AppenderFileHandleLeakTest.java b/src/test/java/net/openhft/chronicle/queue/impl/single/AppenderFileHandleLeakTest.java index f956db4aca..bbb1bcbf52 100644 --- a/src/test/java/net/openhft/chronicle/queue/impl/single/AppenderFileHandleLeakTest.java +++ b/src/test/java/net/openhft/chronicle/queue/impl/single/AppenderFileHandleLeakTest.java @@ -6,7 +6,6 @@ import net.openhft.chronicle.bytes.Bytes; import net.openhft.chronicle.core.Jvm; import net.openhft.chronicle.core.OS; -import net.openhft.chronicle.core.io.BackgroundResourceReleaser; import net.openhft.chronicle.core.io.Closeable; import net.openhft.chronicle.core.time.SystemTimeProvider; import net.openhft.chronicle.core.time.TimeProvider; @@ -42,12 +41,14 @@ import static org.junit.Assume.assumeTrue; public final class AppenderFileHandleLeakTest extends QueueTestCommon { + private static final long FUTURE_TIMEOUT_SECONDS = 120L; + private static final int FILE_CLOSE_RETRY_COUNT = 10; + private static final long FILE_CLOSE_RETRY_PAUSE_MS = 10L; private static final int THREAD_COUNT = Runtime.getRuntime().availableProcessors() * 2; private static final int MESSAGES_PER_THREAD = 50; private static final SystemTimeProvider SYSTEM_TIME_PROVIDER = SystemTimeProvider.INSTANCE; private static final RollCycle ROLL_CYCLE = TEST_SECONDLY; private static final DateTimeFormatter ROLL_CYCLE_FORMATTER = DateTimeFormatter.ofPattern(ROLL_CYCLE.format()).withZone(ZoneId.of("UTC")); - private static final int TRIES = 10; private final ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT, new NamedThreadFactory("test")); @@ -114,13 +115,14 @@ public void appenderAndTailerResourcesShouldBeCleanedUpByGarbageCollection() thr } for (Future future : futures) { - assertTrue(future.get(1, TimeUnit.MINUTES)); + assertTrue(future.get(FUTURE_TIMEOUT_SECONDS, TimeUnit.SECONDS)); } assertFalse(gcGuard.isEmpty()); gcGuard.clear(); } + drainBackgroundCleanup(); Assert.assertTrue(queueFilesAreAllClosed()); finishedNormally = true; } @@ -150,12 +152,14 @@ private void tailerResourcesCanBeReleasedManually0() throws InterruptedException } for (Future future : futures) { - assertTrue(future.get(1, TimeUnit.MINUTES)); + assertTrue(future.get(FUTURE_TIMEOUT_SECONDS, TimeUnit.SECONDS)); } assertFalse(gcGuard.isEmpty()); + gcGuard.clear(); } + drainBackgroundCleanup(); Assert.assertTrue(queueFilesAreAllClosed()); } @@ -175,36 +179,40 @@ public void tailerShouldReleaseFileHandlesAsQueueRolls() throws InterruptedExcep } // StoreFileListener#onAcquired() is called on the background resource releaser thread - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); + int acquiredBefore = storeFileListener.acquiredCounts.size(); storeFileListener.reset(); - final ExcerptTailer tailer = queue.createTailer(); - tailer.toStart(); - int messageCount = 0; - int notFoundAttempts = 5; - while (true) { - try (final DocumentContext ctx = tailer.readingDocument()) { - if (!ctx.isPresent()) { - if (--notFoundAttempts > 0) - continue; - break; - } + try (final ExcerptTailer tailer = queue.createTailer()) { + tailer.toStart(); + int messageCount = 0; + int notFoundAttempts = 5; + while (true) { + try (final DocumentContext ctx = tailer.readingDocument()) { + if (!ctx.isPresent()) { + if (--notFoundAttempts > 0) + continue; + break; + } - messageCount++; + messageCount++; + } } - } - assertEquals(messagesPerThread, messageCount); + assertEquals(messagesPerThread, messageCount); + } // StoreFileListener#onAcquired() is called on the background resource releaser thread - BackgroundResourceReleaser.releasePendingResources(); + drainBackgroundCleanup(); + Jvm.debug().on(getClass(), "storeFileListener " + storeFileListener); assertEquals(acquiredBefore, storeFileListener.acquiredCounts.size()); } + drainBackgroundCleanup(); Assert.assertTrue(queueFilesAreAllClosed()); } @@ -267,7 +275,6 @@ private void tailerShouldOnlyKeepCurrentRollCycleOpen() { } private void assertOnlyCurrentRollCycleIsOpen(long timestamp) { - BackgroundResourceReleaser.releasePendingResources(); /* * "A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected." * @@ -275,7 +282,8 @@ private void assertOnlyCurrentRollCycleIsOpen(long timestamp) { * * See https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/MappedByteBuffer.html */ - GcControls.waitForGcCycle(); + drainBackgroundCleanup(); + final String currentRollCycleName = ROLL_CYCLE_FORMATTER.format(Instant.ofEpochMilli(timestamp)) + ".cq4"; final String absolutePathToCurrentRollCycle = queuePath.toPath().toAbsolutePath().resolve(currentRollCycleName).toString(); Waiters.builder(() -> onlyCurrentRollCycleIsOpen(absolutePathToCurrentRollCycle)) @@ -306,19 +314,26 @@ public void assertReferencesReleased() { } catch (InterruptedException e) { throw new AssertionError(e); } + drainBackgroundCleanup(); super.assertReferencesReleased(); } + @Override + protected void preAfter() { + threadPool.shutdownNow(); + drainBackgroundCleanup(); + } + private boolean queueFilesAreAllClosed() { List openQueueFiles = null; - for (int i = 0; i < TRIES; i++) { - GcControls.waitForGcCycle(); + for (int i = 0; i < FILE_CLOSE_RETRY_COUNT; i++) { + drainBackgroundCleanup(); openQueueFiles = MappedFileUtil.getAllMappedFiles().stream() .filter(str -> str.contains(queuePath.getAbsolutePath())) .collect(Collectors.toList()); if (openQueueFiles.isEmpty()) return true; - Jvm.pause(10); + Jvm.pause(FILE_CLOSE_RETRY_PAUSE_MS); } openQueueFiles.forEach(qf -> From 9f251aafa6ced31c61b2e82c12c481ea598a7005 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Tue, 2 Jun 2026 11:07:13 +0100 Subject: [PATCH 5/7] test: cover subprocess table-store lock acquisition --- .../impl/single/TableStoreWriteLockTest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/test/java/net/openhft/chronicle/queue/impl/single/TableStoreWriteLockTest.java b/src/test/java/net/openhft/chronicle/queue/impl/single/TableStoreWriteLockTest.java index 76835aa22d..82c33ccb0b 100644 --- a/src/test/java/net/openhft/chronicle/queue/impl/single/TableStoreWriteLockTest.java +++ b/src/test/java/net/openhft/chronicle/queue/impl/single/TableStoreWriteLockTest.java @@ -241,6 +241,30 @@ public void forceUnlockIfProcessIsDeadWillSucceedWhenLockIsNotLocked() { } } + @Test(timeout = 30_000) + public void subprocessAcquiresLockBeforeTimeout() throws InterruptedException { + Process process = null; + try (TableStoreWriteLock lock = createTestLock(tableStore, 15_000)) { + process = runLockingProcess(true); + long deadlineNanos = System.nanoTime() + TimeUnit.SECONDS.toNanos(15); + while (!lock.locked()) { + if (System.nanoTime() >= deadlineNanos) + fail("Locking subprocess did not acquire the lock within 15s"); + Jvm.pause(25); + if (Thread.currentThread().isInterrupted()) + throw new InterruptedException("Interrupted while waiting for locking subprocess"); + } + } finally { + if (process != null) { + process.destroy(); + if (!process.waitFor(3, TimeUnit.SECONDS)) { + process.destroyForcibly(); + process.waitFor(3, TimeUnit.SECONDS); + } + } + } + } + private void waitForLockToBecomeLocked(TableStoreWriteLock lock) throws TimeoutException { Pauser p = Pauser.balanced(); while (!lock.locked()) { From 6a48315657b382ad44ded72fef5ed46f6f6e7472 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Tue, 2 Jun 2026 11:07:48 +0100 Subject: [PATCH 6/7] test: track expected queue warnings --- .../queue/impl/single/TestEmptyFile.java | 4 +++- .../backwardstailer/BackwardsTailerJmhState.java | 2 +- .../queue/issue/ChangeRollCycleTest.java | 15 ++++++++++----- .../queue/issue/ReaderResizesFileTest.java | 3 ++- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/test/java/net/openhft/chronicle/queue/impl/single/TestEmptyFile.java b/src/test/java/net/openhft/chronicle/queue/impl/single/TestEmptyFile.java index c5fb27a497..97e74358ca 100644 --- a/src/test/java/net/openhft/chronicle/queue/impl/single/TestEmptyFile.java +++ b/src/test/java/net/openhft/chronicle/queue/impl/single/TestEmptyFile.java @@ -8,6 +8,7 @@ import net.openhft.chronicle.queue.ChronicleQueue; import net.openhft.chronicle.queue.DirectoryUtils; import net.openhft.chronicle.queue.ExcerptTailer; +import net.openhft.chronicle.queue.QueueTestCommon; import org.junit.After; import org.junit.Assume; import org.junit.Before; @@ -20,7 +21,7 @@ import static org.junit.Assert.assertFalse; -public class TestEmptyFile { +public class TestEmptyFile extends QueueTestCommon { private Path tmpDir = DirectoryUtils.tempDir(TestEmptyFile.class.getSimpleName()).toPath(); @SuppressWarnings("ResultOfMethodCallIgnored") @@ -39,6 +40,7 @@ public void cleanup() { @Test(timeout = 30000) public void shouldHandleEmptyFile() { Assume.assumeFalse(OS.isWindows()); + expectException("Failback to readonly tablestore"); try (final ChronicleQueue queue = ChronicleQueue.singleBuilder(tmpDir) .testBlockSize() diff --git a/src/test/java/net/openhft/chronicle/queue/impl/single/stress/backwardstailer/BackwardsTailerJmhState.java b/src/test/java/net/openhft/chronicle/queue/impl/single/stress/backwardstailer/BackwardsTailerJmhState.java index ba86749e92..2e01a441d5 100644 --- a/src/test/java/net/openhft/chronicle/queue/impl/single/stress/backwardstailer/BackwardsTailerJmhState.java +++ b/src/test/java/net/openhft/chronicle/queue/impl/single/stress/backwardstailer/BackwardsTailerJmhState.java @@ -16,7 +16,7 @@ import static net.openhft.chronicle.core.io.Closeable.closeQuietly; -class BackwardsTailerJmhState { +class BackwardsTailerJmhState extends QueueTestCommon { private static final Logger log = LoggerFactory.getLogger(BackwardsTailerToEndPerfAcceptanceTest.class); diff --git a/src/test/java/net/openhft/chronicle/queue/issue/ChangeRollCycleTest.java b/src/test/java/net/openhft/chronicle/queue/issue/ChangeRollCycleTest.java index da40605080..5f302e78a4 100644 --- a/src/test/java/net/openhft/chronicle/queue/issue/ChangeRollCycleTest.java +++ b/src/test/java/net/openhft/chronicle/queue/issue/ChangeRollCycleTest.java @@ -5,10 +5,7 @@ import net.openhft.chronicle.core.OS; import net.openhft.chronicle.core.io.IOTools; -import net.openhft.chronicle.queue.ChronicleQueue; -import net.openhft.chronicle.queue.ExcerptAppender; -import net.openhft.chronicle.queue.ExcerptTailer; -import net.openhft.chronicle.queue.RollCycles; +import net.openhft.chronicle.queue.*; import net.openhft.chronicle.wire.DocumentContext; import org.junit.Test; @@ -20,7 +17,7 @@ * It checks the compatibility and visibility of data written with one Roll Cycle * and accessed with another. */ -public class ChangeRollCycleTest { +public class ChangeRollCycleTest extends QueueTestCommon { @Test public void changeRollCycleWithReadOnlyTailer() { @@ -38,6 +35,14 @@ public void changeRollCycleWithReadWriteTailer() { * @param readOnly whether the tailer should be in read-only mode */ private void testChangeRollCycle(boolean readOnly) { + if (readOnly) { + expectException("Failback to readonly tablestore"); + expectException("Overriding roll cycle from FAST_HOURLY to WEEKLY"); + } else { + expectException("Overriding roll cycle from WEEKLY to FAST_DAILY"); + expectException("Overriding roll cycle from FAST_HOURLY to FAST_DAILY"); + } + // Define the queue path String queuePath = OS.getTarget() + "/changeRollCycle-" + System.nanoTime(); diff --git a/src/test/java/net/openhft/chronicle/queue/issue/ReaderResizesFileTest.java b/src/test/java/net/openhft/chronicle/queue/issue/ReaderResizesFileTest.java index cfb5be2159..4f48b01883 100644 --- a/src/test/java/net/openhft/chronicle/queue/issue/ReaderResizesFileTest.java +++ b/src/test/java/net/openhft/chronicle/queue/issue/ReaderResizesFileTest.java @@ -11,6 +11,7 @@ import net.openhft.chronicle.queue.ChronicleQueue; import net.openhft.chronicle.queue.ExcerptAppender; import net.openhft.chronicle.queue.ExcerptTailer; +import net.openhft.chronicle.queue.QueueTestCommon; import net.openhft.chronicle.queue.impl.single.SingleChronicleQueue; import net.openhft.chronicle.queue.impl.single.SingleChronicleQueueBuilder; import net.openhft.chronicle.queue.rollcycles.TestRollCycles; @@ -26,7 +27,7 @@ import static org.junit.jupiter.api.Assertions.*; -public class ReaderResizesFileTest { +public class ReaderResizesFileTest extends QueueTestCommon { private static final File QUEUE_DIR = new File(OS.getTarget(), "ReaderResizesFileTest-" + System.nanoTime()); @After From 3840e2150f06919e49ae132d932ccc1f24ab06ee Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Tue, 2 Jun 2026 11:08:08 +0100 Subject: [PATCH 7/7] test: guard security manager reset on newer Java --- .../chronicle/queue/ChronicleHistoryReaderMainTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/net/openhft/chronicle/queue/ChronicleHistoryReaderMainTest.java b/src/test/java/net/openhft/chronicle/queue/ChronicleHistoryReaderMainTest.java index 9e117f9640..daf1305192 100644 --- a/src/test/java/net/openhft/chronicle/queue/ChronicleHistoryReaderMainTest.java +++ b/src/test/java/net/openhft/chronicle/queue/ChronicleHistoryReaderMainTest.java @@ -43,7 +43,8 @@ public void setUp() { @After public void tearDown() { - System.setSecurityManager(null); + if (Jvm.majorVersion() < 17) + System.setSecurityManager(null); } @Test