diff --git a/AGENTS.md b/AGENTS.md index 6e21f4476..ae1e74b04 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,23 +8,23 @@ LLM-based agents can accelerate development only if they respect our house rules ## Language & character-set policy -| Requirement | Rationale | -|--------------|-----------| -| **British English** spelling (`organisation`, `licence`, *not* `organization`, `license`) except technical US spellings like `synchronized` | Keeps wording consistent with Chronicle's London HQ and existing docs. See the University of Oxford style guide for reference. | -| **ASCII-7 only** (code-points 0-127). Avoid smart quotes, non-breaking spaces and accented characters. | ASCII-7 survives every toolchain Chronicle uses, incl. low-latency binary wire formats that expect the 8th bit to be 0. | -| If a symbol is not available in ASCII-7, use a textual form such as `micro-second`, `>=`, `:alpha:`, `:yes:`. This is the preferred approach and Unicode must not be inserted. | Extended or '8-bit ASCII' variants are *not* portable and are therefore disallowed. | +| Requirement | Rationale | +|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------| +| **British English** spelling (`organisation`, `licence`, *not* `organization`, `license`) except technical US spellings like `synchronized` | Keeps wording consistent with Chronicle's London HQ and existing docs. See the University of Oxford style guide for reference. | +| **ISO-8859-1** (code-points 0-255). Avoid smart quotes, non-breaking spaces and accented characters. | ISO-8859-1 survives every toolchain Chronicle uses, incl. low-latency binary wire formats that expect the 8th bit to be 0. | +| If a symbol is not available in ISO-8859-1, use a textual form such as `micro-second`, `>=`, `:alpha:`, `:yes:`. This is the preferred approach and Unicode must not be inserted. | Extended or '8-bit ASCII' variants are *not* portable and are therefore disallowed. | ## Javadoc guidelines **Goal:** Every Javadoc block should add information you cannot glean from the method signature alone. Anything else is noise and slows readers down. -| Do | Don't | -|----|-------| -| State *behavioural contracts*, edge-cases, thread-safety guarantees, units, performance characteristics and checked exceptions. | Restate the obvious ("Gets the value", "Sets the name"). | -| Keep the first sentence short; it becomes the summary line in aggregated docs. | Duplicate parameter names/ types unless more explanation is needed. | -| Prefer `@param` for *constraints* and `@throws` for *conditions*, following Oracle's style guide. | Pad comments to reach a line-length target. | -| Remove or rewrite autogenerated Javadoc for trivial getters/setters. | Leave stale comments that now contradict the code. | +| Do | Don't | +|---------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------| +| State *behavioural contracts*, edge-cases, thread-safety guarantees, units, performance characteristics and checked exceptions. | Restate the obvious ("Gets the value", "Sets the name"). | +| Keep the first sentence short; it becomes the summary line in aggregated docs. | Duplicate parameter names/ types unless more explanation is needed. | +| Prefer `@param` for *constraints* and `@throws` for *conditions*, following Oracle's style guide. | Pad comments to reach a line-length target. | +| Remove or rewrite autogenerated Javadoc for trivial getters/setters. | Leave stale comments that now contradict the code. | The principle that Javadoc should only explain what is *not* manifest from the signature is well-established in the wider Java community. @@ -55,12 +55,13 @@ mvn -q verify ## Project requirements -See the [Decision Log](src/main/adoc/decision-log.adoc) for the latest project decisions. -See the [Project Requirements](src/main/adoc/project-requirements.adoc) for details on project requirements. +See the [Decision Log](src/main/docs/decision-log.adoc) for the latest project decisions. +See the [Project Requirements](src/main/docs/project-requirements.adoc) for details on project requirements. ## Elevating the Workflow with Real-Time Documentation -Building upon our existing Iterative Workflow, the newest recommendation is to emphasise *real-time updates* to documentation. +Building upon our existing Iterative Workflow, the newest recommendation is to emphasise *real-time updates* to +documentation. Ensure the relevant `.adoc` files are updated when features, requirements, implementation details, or tests change. This tight loop informs the AI accurately and creates immediate clarity for all team members. @@ -75,41 +76,54 @@ This tight loop informs the AI accurately and creates immediate clarity for all ### Best Practices -* **Maintain Sync**: Keep documentation (AsciiDoc), tests, and code synchronised in version control. Changes in one area should prompt reviews and potential updates in the others. -* **Doc-First for New Work**: For *new* features or requirements, aim to update documentation first, then use AI to help produce or refine corresponding code and tests. For refactoring or initial bootstrapping, updates might flow from code/tests back to documentation, which should then be reviewed and finalised. -* **Small Commits**: Each commit should ideally relate to a single requirement or coherent change, making reviews easier for humans and AI analysis tools. -- **Team Buy-In**: Encourage everyone to review AI outputs critically and contribute to maintaining the synchronicity of all artefacts. +* **Maintain Sync**: Keep documentation (AsciiDoc), tests, and code synchronised in version control. Changes in one area + should prompt reviews and potential updates in the others. +* **Doc-First for New Work**: For *new* features or requirements, aim to update documentation first, then use AI to help + produce or refine corresponding code and tests. For refactoring or initial bootstrapping, updates might flow from + code/tests back to documentation, which should then be reviewed and finalised. +* **Small Commits**: Each commit should ideally relate to a single requirement or coherent change, making reviews easier + for humans and AI analysis tools. + +- **Team Buy-In**: Encourage everyone to review AI outputs critically and contribute to maintaining the synchronicity of + all artefacts. ## AI Agent Guidelines When using AI agents to assist with development, please adhere to the following guidelines: -* **Respect the Language & Character-set Policy**: Ensure all AI-generated content follows the British English and ASCII-7 guidelines outlined above. -Focus on Clarity: AI-generated documentation should be clear and concise and add value beyond what is already present in the code or existing documentation. -* **Avoid Redundancy**: Do not generate content that duplicates existing documentation or code comments unless it provides additional context or clarification. -* **Review AI Outputs**: Always review AI-generated content for accuracy, relevance, and adherence to the project's documentation standards before committing it to the repository. +* **Respect the Language & Character-set Policy**: Ensure all AI-generated content follows the British English and + ISO-8859-1 guidelines outlined above. + Focus on Clarity: AI-generated documentation should be clear and concise and add value beyond what is already present + in the code or existing documentation. +* **Avoid Redundancy**: Do not generate content that duplicates existing documentation or code comments unless it + provides additional context or clarification. +* **Review AI Outputs**: Always review AI-generated content for accuracy, relevance, and adherence to the project's + documentation standards before committing it to the repository. ## Company-Wide Tagging -This section records **company-wide** decisions that apply to *all* Chronicle projects. All identifiers use the --xxx prefix. The `xxx` are unique across in the same Scope even if the tags are different. Component-specific decisions live in their xxx-decision-log.adoc files. +This section records **company-wide** decisions that apply to *all* Chronicle projects. All identifiers use +the --xxx prefix. The `xxx` are unique across in the same Scope even if the tags are different. +Component-specific decisions live in their xxx-decision-log.adoc files. ### Tag Taxonomy (Nine-Box Framework) -To improve traceability, we adopt the Nine-Box taxonomy for requirement and decision identifiers. These tags are used in addition to the existing ALL prefix, which remains reserved for global decisions across every project. +To improve traceability, we adopt the Nine-Box taxonomy for requirement and decision identifiers. These tags are used in +addition to the existing ALL prefix, which remains reserved for global decisions across every project. .Adopt a Nine-Box Requirement Taxonomy -|Tag | Scope | Typical examples | -|----|-------|------------------| -|FN |Functional user-visible behaviour | Message routing, business rules | -|NF-P |Non-functional - Performance | Latency budgets, throughput targets | -|NF-S |Non-functional - Security | Authentication method, TLS version | -|NF-O |Non-functional - Operability | Logging, monitoring, health checks | -|TEST |Test / QA obligations | Chaos scenarios, benchmarking rigs | -|DOC |Documentation obligations | Sequence diagrams, user guides | -|OPS |Operational / DevOps concerns | Helm values, deployment checklist | -|UX |Operator or end-user experience | CLI ergonomics, dashboard layouts | -|RISK |Compliance / risk controls | GDPR retention, audit trail | +| Tag | Scope | Typical examples | +|------|-----------------------------------|-------------------------------------| +| FN | Functional user-visible behaviour | Message routing, business rules | +| NF-P | Non-functional - Performance | Latency budgets, throughput targets | +| NF-S | Non-functional - Security | Authentication method, TLS version | +| NF-O | Non-functional - Operability | Logging, monitoring, health checks | +| TEST | Test / QA obligations | Chaos scenarios, benchmarking rigs | +| DOC | Documentation obligations | Sequence diagrams, user guides | +| OPS | Operational / DevOps concerns | Helm values, deployment checklist | +| UX | Operator or end-user experience | CLI ergonomics, dashboard layouts | +| RISK | Compliance / risk controls | GDPR retention, audit trail | `ALL-*` stays global, case-exact tags. Pick one primary tag if multiple apply. diff --git a/LICENSE.adoc b/LICENSE.adoc index eb12fcc48..f45056642 100644 --- a/LICENSE.adoc +++ b/LICENSE.adoc @@ -1,14 +1,9 @@ - == Copyright 2016-2025 chronicle.software -Licensed under the *Apache License, Version 2.0* (the "License"); -you may not use this file except in compliance with the License. +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. +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. diff --git a/README.adoc b/README.adoc index e3a56d7d5..c3a8852f7 100644 --- a/README.adoc +++ b/README.adoc @@ -20,8 +20,7 @@ toc::[] This library provides high performance event loop implementations and utility functions to help with threading and concurrency. Concurrency is _hard_, and event loops provide an abstraction to make dealing with concurrency easier. -Context switching between threads is expensive and best practice in a low latency system is to keep your latency-sensitive operations executing on a small number of "fast" threads -and the event loop abstraction fits well with this approach. +Context switching between threads is expensive and best practice in a low latency system is to keep your latency-sensitive operations executing on a small number of "fast" threads and the event loop abstraction fits well with this approach. For best latency these fast threads will be busy-spinning i.e. consuming a whole core, and the core running the fast thread is isolated from the OS and other applications. @@ -35,9 +34,8 @@ This library contains a number of event loop implementations and additional feat == Event Handlers and Event Loops An event loop can have multiple event handlers installed on it and the event loop will repeatedly execute these event handlers. -Each event handler is guaranteed to be called on only one thread. Event handlers are expected to perform a chunk of work -quickly and return without blocking, although the <> does provide a mechanism to support blocking -event handlers - see +Each event handler is guaranteed to be called on only one thread. +Event handlers are expected to perform a chunk of work quickly and return without blocking, although the <> does provide a mechanism to support blocking event handlers - see link:https://github.com/OpenHFT/Chronicle-Core/blob/ea/src/main/java/net/openhft/chronicle/core/threads/HandlerPriority.java[`HandlerPriority`]. See @@ -45,8 +43,7 @@ link:https://github.com/OpenHFT/Chronicle-Core/blob/ea/src/main/java/net/openhft and link:https://github.com/OpenHFT/Chronicle-Core/blob/ea/src/main/java/net/openhft/chronicle/core/threads/EventHandler.java[`EventHandler`]. -Figure 1 -illustrates a diagram of an event loop that serves several event handlers sequentially. +Figure 1 illustrates a diagram of an event loop that serves several event handlers sequentially. [#img-eventloop] .Event Loop and Event Handlers @@ -57,10 +54,9 @@ image::docs/images/EventLoop.png[1000,600] In this section we explore common event handler use cases ==== Implement event handler + An event handler can be created by implementing the `EventHandler` interface and overriding its `action()` method so that it executes the required work. -The `action()` method returns a boolean value signifying whether some work was done by the `action()` - an -example is an event handler that tries to poll from a Chronicle Queue - the return value of the `action()` should -indicate if the read succeeded and a message was processed. +The `action()` method returns a boolean value signifying whether some work was done by the `action()` - an example is an event handler that tries to poll from a Chronicle Queue - the return value of the `action()` should indicate if the read succeeded and a message was processed. NOTE: The event loop considers this return value by using a heuristic: if the `action()` did some work, it is likely to do some more work next time, and so we should call it again as soon as we can. If it did not do some work, it is less likely to do work next time, so it may appropriate to yield or pause before calling again - see <>. @@ -80,6 +76,7 @@ public final class ExampleEventHandler implements EventHandler { ---- ==== Adding to event loop + call the `addHandler` method of the event loop, see also <> [source,java] @@ -88,8 +85,8 @@ el.addHandler(eh0); ---- ==== Removing an event handler from an eventLoop -When an event handler wants to remove itself -from the event loop, its `action()` method should throw `InvalidEventHandlerException`. + +When an event handler wants to remove itself from the event loop, its `action()` method should throw `InvalidEventHandlerException`. The `InvalidEventHandlerException.reusable()` method returns a reusable, pre-created, `InvalidEventHandlerException` that is unmodifiable and contains no stack trace. The below event handler uninstalls itself after being called 30 times. @@ -115,6 +112,7 @@ These are aggregated together in the link:src/main/java/net/openhft/chronicle/th The `EventGroup` also automatically enables <>.. ==== Creating event loop + event group is created by calling the using the link:src/main/java/net/openhft/chronicle/threads/EventGroupBuilder.java[`EventGroupBuilder`]. Basic example shown below: @@ -162,8 +160,7 @@ The second use of `HandlerPriority` is to enable each (child) event loop to dete Chronicle Threads provides a number of implementations of the link:src/main/java/net/openhft/chronicle/threads/Pauser.java[`Pauser`] and it is straightforward for the user to implement their own if need be. -The `Pauser` allows the developer to choose an appropriate trade-off between latency vs CPU consumption for when -an `EventLoop` is running events which exhibit "bursty" behaviour. +The `Pauser` allows the developer to choose an appropriate trade-off between latency vs CPU consumption for when an `EventLoop` is running events which exhibit "bursty" behaviour. The recommended way to use `Pauser` - and this is how Chronicle Thread's event loop implementations use it: @@ -184,13 +181,10 @@ The `Pauser` implementation can choose to yield, pause (with back off if require In the context of the heuristic in <> above - if an `EventHandler` does no work, then it may well not need to do any work for a while, as events often occur in bursts in the real world. -In this case it makes sense for the `Pauser` to keep track of how many times -it has been called, and progressively implement longer pauses every time its `pause()` is called. -This behaviour allows a back off pauser to strike a reasonable balance between handling bursts of events quickly, -but backing off and reducing CPU consumption in case of gap in incoming events. +In this case it makes sense for the `Pauser` to keep track of how many times it has been called, and progressively implement longer pauses every time its `pause()` is called. +This behaviour allows a back off pauser to strike a reasonable balance between handling bursts of events quickly, but backing off and reducing CPU consumption in case of gap in incoming events. -A good example of a back off `Pauser` is the `LongPauser` which will busy-loop for `minBusy` events (allowing the event loop to respond -quickly if a new event arrives immediately), then will yield for `minCount` times before it sleeping for `minTime` increasing up to `maxTime`. +A good example of a back off `Pauser` is the `LongPauser` which will busy-loop for `minBusy` events (allowing the event loop to respond quickly if a new event arrives immediately), then will yield for `minCount` times before it sleeping for `minTime` increasing up to `maxTime`. === TimingPauser diff --git a/docs/images/source/image1.svg b/docs/images/source/image1.svg index d6b94ffca..f7e15af55 100644 --- a/docs/images/source/image1.svg +++ b/docs/images/source/image1.svg @@ -1,9 +1,11 @@ + id="Lager_1" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" + viewBox="0 0 658.2 405.8" + style="enable-background:new 0 0 658.2 405.8;" xml:space="preserve"> - - + + - - - - + + - + - - - -Event Handler 1 - -Event Handler 3 - -Event Handler 2 - -Event Handler 4 - -Event Handler 5 -Event Loop + + Event Handler 1 + + Event Handler 3 + + Event Handler 2 + + Event Handler 4 + + Event Handler 5 + Event Loop diff --git a/pom.xml b/pom.xml index 993f098fd..1a8f5cc73 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,8 @@ ~ limitations under the License. --> - + 4.0.0 @@ -23,7 +24,7 @@ net.openhft java-parent-pom 1.27ea1 - + chronicle-threads @@ -32,8 +33,18 @@ Chronicle-Threads bundle - openhft - https://sonarcloud.io + openhft + https://sonarcloud.io + + 3.6.0 + 8.45.1 + 4.8.6.6 + 1.14.0 + 3.28.0 + 0.8.14 + 0.80 + 0.70 + 66.3 @@ -163,7 +174,8 @@ 2.27ea0 https://teamcity.chronicle.software/repository/download - 100.0 + ${binary.compatibility.percentage.required} + @@ -246,6 +258,154 @@ + + code-review + + false + + + + 0.0 + 0.0 + 0.0 + + + + + net.openhft + binary-compatibility-enforcer-plugin + + 0.0 + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${checkstyle.version} + + + com.puppycrawl.tools + checkstyle + ${puppycrawl.version} + + + + + checkstyle + + check + + verify + + + + src/main/config/checkstyle.xml + true + true + warning + + + + + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs.version} + + + com.h3xstream.findsecbugs + findsecbugs-plugin + ${findsecbugs.version} + + + + + spotbugs + + check + + verify + + + + Max + Low + true + src/main/config/spotbugs-exclude.xml + + + + + org.apache.maven.plugins + maven-pmd-plugin + ${maven-pmd-plugin.version} + + + pmd + + check + + verify + + + + true + true + + src/main/config/pmd-ruleset.xml + + src/main/config/pmd-exclude.properties + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + prepare-agent + + prepare-agent + + + + report + + report + + verify + + + check + + check + + verify + + + + BUNDLE + + + LINE + COVEREDRATIO + ${jacoco.line.coverage} + + + BRANCH + COVEREDRATIO + ${jacoco.branch.coverage} + + + + + + + + + + + sonar diff --git a/src/main/adoc/project-requirements.adoc b/src/main/adoc/project-requirements.adoc deleted file mode 100644 index cd9f87894..000000000 --- a/src/main/adoc/project-requirements.adoc +++ /dev/null @@ -1,198 +0,0 @@ -= Chronicle Threads – Functional Requirements Specification -:revnumber: 1.0 -:revdate: 2025-05-25 -:toc: -:source-highlighter: rouge -:lang: en=-GB - -== 1 Purpose and Scope - -This document specifies the *functional requirements* for the -*Chronicle Threads* library (OpenHFT / Chronicle Software). -It is aimed at architects, developers, and performance engineers who -intend to use Chronicle Threads as the execution engine for -ultra-low-latency, event-driven Java systems. - -*Out of scope* are: -* Detailed Non-Functional Requirement (NFR) specifications beyond the key performance targets identified in Section 5. -* Licensing and commercial support details. -* The detailed product roadmap (though Section 8 outlines potential future enhancements). - -== 2 Definitions - -[cols="1,3"] -|=== -|*Term* |*Meaning* - -|*EventLoop* |A single-threaded loop that repeatedly invokes `EventHandler` instances. -|*EventHandler* |Application-provided component that implements `action()`; executed by an `EventLoop`. -|*EventGroup* |A container that manages one or more `EventLoop`s, potentially including a dedicated monitor loop. -|*Pauser* |Strategy object that controls how an idle loop waits (e.g., busy-spin, yield, sleep). -|*Fast Thread* |A thread—usually pinned to an isolated CPU core—running a latency-critical event loop. -|*Hot Path* / *Fast Path* |The code execution path that is performance-critical and frequently executed, where low latency and minimal overhead are paramount. -|*Loop-Block Monitor* |Background handler that measures `EventHandler` run-time and logs outliers exceeding a configured threshold. -|*NUMA* |Non-Uniform Memory Access; a memory architecture where memory access time depends on the memory location relative to a processor. -|*Chronicle Affinity* |A library used for managing thread affinity, allowing threads to be pinned to specific CPU cores. -|=== - -== 3 Overall Architectural Goals - -* Minimise **end-to-end latency** and **jitter** for event processing. -* Provide a **deterministic single-threaded** execution model for event handlers within an `EventLoop`, removing the need for locks in the hot path of handler logic. -* Allow **fine-grained control** of the latency versus CPU consumption trade-off. -* Integrate effectively with other Chronicle components (e.g., Chronicle Queue, Chronicle Map, Chronicle Wire, Chronicle Services, Chronicle Tune). - -== 4 High-Level Functional Requirements - -=== 4.1 Event‐Loop Lifecycle and Configuration - -. *Creation and Configuration* -*THR-FN-001* The library SHALL provide builder APIs (e.g., `EventLoopBuilder`, `EventGroupBuilder`) for fluent and immutable configuration of event loops and groups. -. *Start/Stop Operations* -*THR-FN-002* An `EventLoop` or `EventGroup` SHALL support idempotent `start()` and graceful `close()` operations. A graceful `close()` implies that active handlers are allowed to complete their current action, and associated resources are released. -. *Non-Restartable Loops* -*THR-FN-003* Attempting to `start()` an `EventLoop` or `EventGroup` that has already been `close()`d SHALL be rejected or have no effect, as loops are not designed to be restartable. - -=== 4.2 Handler Management - -. *Dynamic Registration* -*THR-FN-004* Clients SHALL be able to add an `EventHandler` to a running `EventGroup` at runtime. -. *Handler Priority* -*THR-FN-005* Each `EventHandler` SHALL declare a `HandlerPriority`. The system SHALL support a range of priorities influencing execution order and/or loop assignment. (Refer to `net.openhft.chronicle.core.threads.HandlerPriority` enum for the exhaustive list of priorities, e.g., `HIGH`, `MEDIUM`, `LOW`, `TIMER`, `BLOCKING`, `REPLICATION`, `CONCURRENT`, `MONITOR`, `DAEMON`). -. *Execution Contract* -*THR-FN-006* An `EventHandler`'s `action()` method MUST be invoked serially on the `EventLoop`'s dedicated thread. -*THR-FN-007* The `action()` method MUST return a boolean: `true` if useful work was done (suggesting the handler may have more immediate work), `false` otherwise. -. *Self-Deregistration* -*THR-FN-008* An `EventHandler` MAY request its own deregistration from the `EventLoop` by throwing `net.openhft.chronicle.core.threads.InvalidEventHandlerException`. -. *Error Isolation and Reporting* -*THR-NF-O-009* Unchecked exceptions thrown by an `EventHandler`'s `action()` method SHALL NOT terminate the `EventLoop` itself. The offending handler SHALL be removed, and the error SHALL be reported using the standard `net.openhft.chronicle.core.Jvm.warn()` logging mechanism by default. - -=== 4.3 Idle Strategy (Pauser) - -. *Supported Pauser Modes* -*THR-FN-010* The library SHALL ship with a set of standard `Pauser` strategies, configurable via `PauserMode` enum values, including at least: `BUSY`, `TIMED_BUSY`, `YIELDING`, `BALANCED`, `MILLI`, and `SLEEPY`. (Refer to `net.openhft.chronicle.threads.PauserMode.java` and `README.adoc` for details on each mode). -. *Custom Pauser Pluggability* -*THR-FN-011* Applications SHALL be able to supply custom `Pauser` implementations via programmatic configuration (e.g., through builder APIs like `EventGroupBuilder.withPauser(Pauser customPauser)`). -. *Adaptive Back-off Parameterisation* -*THR-FN-012* Adaptive pausers (such as `LongPauser`, which underpins modes like `BALANCED` and `SLEEPY`) SHALL allow parameterisation of their back-off behaviour, including aspects like minimum busy-spin duration, yield duration, and minimum/maximum sleep times. -. *TimingPauser Support* -*THR-NF-O-013* `TimingPauser` implementations (e.g., `LongPauser`, `BusyTimedPauser`) SHALL expose pause-related metrics (e.g., total time paused via `timePaused()`, pause count via `countPaused()`) and SHALL optionally throw `java.util.concurrent.TimeoutException` when a configured timeout duration is exceeded during a timed pause. -. *Zero-Allocation Hot Path* -*THR-NF-P-014* The hot path methods `Pauser.pause()` and `Pauser.reset()` for built-in pausers SHALL NOT allocate heap objects. - -=== 4.4 Thread Affinity and CPU Isolation - -. *CPU Affinity API* -*THR-FN-015* The library SHALL provide mechanisms to bind `EventLoop` threads to specific CPU cores, utilizing Chronicle Affinity. This SHALL be configurable via builder APIs (e.g., `EventGroupBuilder.withBinding(String affinity)`). -. *CPU Isolation Guidance* -*THR-DOC-016* Documentation (`README.adoc`) SHALL recommend OS-level isolation of CPU cores (e.g., using `isolcpus` on Linux) for `EventLoop` threads when latency-sensitive pausers (like `PauserMode.BUSY` or `PauserMode.TIMED_BUSY`) are used, to minimize jitter. -. *NUMA Awareness* -*THR-FN-017* Builders SHOULD allow configuration that facilitates pinning `EventLoop` threads to specific NUMA nodes. This is typically achieved via the `binding` string syntax provided to Chronicle Affinity, which can specify core layouts respecting NUMA topology. - -=== 4.5 Monitoring and Diagnostics - -. *Loop Block Monitoring* -*THR-NF-O-018* For an `EventGroup`, a dedicated monitor loop (e.g., `MonitorEventLoop`) SHALL, by default, measure the wall-clock duration of `EventHandler` invocations on other event loops within the group. -*THR-NF-O-019* If an `EventHandler`'s `action()` method duration exceeds a configurable threshold (defaulting to a value specified by `loop.block.threshold.ns`), the framework SHALL capture and log a stack trace of the event loop thread executing that handler. -. *Monitoring Configuration Toggles* -*THR-OPS-020* Loop block monitoring MAY be disabled globally via a system property (e.g., `disableLoopBlockMonitor=true`). The monitoring interval SHALL also be configurable (e.g., `MONITOR_INTERVAL_MS`). -. *Pauser Metrics Accessibility* -*THR-NF-O-021* Key metrics from `Pauser` instances, such as `timePaused()` and `countPaused()`, SHALL be programmatically accessible. Implementations of `PauserMonitorFactory` MAY provide handlers to monitor and log these. -. *Low-Overhead Monitoring* -*THR-NF-P-022* When all `EventHandler` invocations are within their execution thresholds, the loop block monitoring mechanism MUST impose negligible overhead (target < 1 us overhead per monitored loop per second, excluding logging actions if a threshold is breached). - -=== 4.6 Configuration and Deployment - -. *System Property Overrides* -*THR-OPS-023* Default behaviours and parameters (e.g., pauser modes, monitor intervals, logging thresholds) SHALL be overridable via JVM system properties. (Refer to `systemProperties.adoc` for a comprehensive list; Appendix B provides examples). -. *Programmatic Configuration Precedence* -*THR-OPS-024* Programmatic configurations provided via builder APIs SHALL take precedence over global system property settings. -. *Graceful JVM Shutdown Hook* -*THR-OPS-025* An `EventGroup` instance, when configured appropriately (e.g., via `((net.openhft.chronicle.core.io.AbstractCloseable) eventGroup).addShutdownHook(true)`), SHALL attempt to `close()` automatically on JVM exit. -. *JDK Compatibility* -*THR-NF-O-026* Chronicle Threads SHALL run on Java 11 LTS or newer. The library SHALL default to using platform threads. While aiming for compatibility with JDK Project Loom (virtual threads) for suitable use cases (e.g., blocking handlers not requiring core affinity), full support and affinity guarantees with virtual threads depend on JDK evolution and are subject to considerations detailed in Section 8 (Open Issues). - -== 5 Key Performance Targets -These non-functional targets guide the design and optimization of Chronicle Threads for ultra-low-latency scenarios. -They are primarily applicable when using performance-oriented pausers (e.g., `BUSY`) on suitably configured systems (e.g., with isolated cores). - -*THR-NF-P-027* **Latency:** Single-hop message processing through an event handler SHALL target <= 10 us at the 99.99th percentile on commodity x86_64 hardware with a busy pauser and isolated cores. -*THR-NF-P-028* **Jitter:** Peak-to-peak variation in handler execution time SHALL target <= 2 us under steady load conditions for well-behaved handlers. -*THR-NF-P-029* **Throughput:** A single "fast core" `EventLoop` SHALL be capable of processing >= 5 million simple (e.g., 64-byte payload) events per second. -*THR-NF-P-030* **Heap Allocation:** In the hot path of event processing (i.e., within the `EventLoop` and `EventHandler.action()` calls for common use cases), heap allocation SHALL target <= 0.1 Bytes per event on average. Pauser hot paths are covered by THR-NF-P-014. -*THR-NF-P-031* **CPU Utilisation:** -* `PauserMode.BUSY` SHALL consume 100% of its assigned (and ideally isolated) CPU core. -* Adaptive pauser modes (e.g., `BALANCED`, `SLEEPY`) SHALL reduce CPU consumption significantly (e.g., target < 20%) when the event loop is idle. - -== 6 Use-Case Scenarios - -=== 6.1 Matching Engine -A financial matching engine processes incoming orders and market data. -*Multiple* `EventHandler` instances (e.g., for order book management, risk checks, trade execution) share a `HIGH` priority `EventLoop` pinned to an isolated CPU core (Core 2). (Illustrates: THR-FN-005, THR-FN-006, THR-FN-015, THR-NF-P-027) -A `MEDIUM` priority `EventLoop` on a separate core (Core 3) handles journalling of trades and significant events to Chronicle Queue. -(Illustrates: THR-FN-005, THR-FN-015) -A `MONITOR` loop, possibly on a non-isolated core, supervises both application loops. -(Illustrates: THR-NF-O-018) - -=== 6.2 Bursty Telemetry Ingestion -An `EventLoop` configured with a `PauserMode.BALANCED` ingests UDP packets containing telemetry data. -The `EventHandler` parses these packets (e.g., using Chronicle Wire) and forwards them to a Chronicle Queue for downstream processing. -During off-peak hours, CPU usage for this loop drops significantly (e.g., below 5%) due to the pauser's adaptive back-off. -During bursts, it processes events with low latency. -(Illustrates: THR-FN-010, THR-FN-012, THR-NF-P-031) - -== 7 References -* Chronicle Threads `README.adoc` (Provides overview, usage examples, and pauser details) -* `systemProperties.adoc` (Comprehensive list of configurable JVM system properties) -* `net.openhft.chronicle.core.threads.HandlerPriority` Javadoc (Definitive list of handler priorities) -* `net.openhft.chronicle.threads.PauserMode` Javadoc (Definitive list of pauser modes) -* Chronicle Affinity library documentation (For details on CPU binding syntax and capabilities) - -== 8 Open Issues / Future Enhancements -This section lists areas identified for potential future development or requiring further investigation. -They are not committed functional requirements for the current version. - -* Support for **carrier-thread reuse** with JDK virtual threads while retaining affinity guarantees where possible. -* First-class asynchronous I/O helper components to better integrate frameworks like Netty or `java.nio.channels` directly with `EventLoop`s. -* Live reconfiguration of `Pauser` parameters via JMX or a similar management interface. -* Enhanced built-in metrics publication mechanisms beyond basic pauser counters and loop-block logs. - -== 9 Appendices - -=== A Builder Example - -[source,java] ----- -EventGroup eg = EventGroup.builder() - .withName("MatchingEngineGroup") // Sets the base name for the EventGroup and its child loops - .withLoopCount(2) // Example: might influence number of certain types of loops if applicable - .withPauserMode(PauserMode.BUSY) // Sets default pauser for core loops - .withPriorities(EnumSet.of(HandlerPriority.HIGH, HandlerPriority.MEDIUM, HandlerPriority.MONITOR)) // Specify which handler priorities this group will support - .build(); - -// Add application-specific handlers -eg.addHandler(new MatchingEngineOrderHandler()); // Assuming this implements EventHandler -eg.addHandler(new JournalWriterHandler()); // Assuming this implements EventHandler - -eg.start(); - -// finally -eg.close(); ----- - -=== B System Properties Quick Reference -This is a non-exhaustive list of key system properties. -For a comprehensive list, refer to the `systemProperties.adoc` document. - -[cols="1,3"] -|=== -|*Property* |*Effect / Default (Illustrative)* - -|`pauserMode` |Global override for default pauser selection (e.g., `busy`, `balanced`). Used if not specified by builder. -|`loop.block.threshold.ns` |Nanoseconds before a handler invocation is flagged as a block (Default: 100,000,000 ns = 100 ms). -|`MONITOR_INTERVAL_MS` |Sampling interval for the monitor loop (Default: 100 ms). -|`disableLoopBlockMonitor` |Set to `true` to disable loop block monitoring (Default: `false`). -|`eventGroup.conc.threads` | Default number of threads for `CONCURRENT` priority handlers (Default: Varies, e.g., CPU cores / 4). -|`chronicle.disk.monitor.disable` | Set to `true` to disable the disk space monitor (Default: `false`). -|`chronicle.disk.monitor.threshold.percent` | Disk usage percentage above which warnings are issued (Default: 5%). -|=== diff --git a/src/main/config/checkstyle-suppressions.xml b/src/main/config/checkstyle-suppressions.xml new file mode 100644 index 000000000..16fa0e334 --- /dev/null +++ b/src/main/config/checkstyle-suppressions.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/config/checkstyle.xml b/src/main/config/checkstyle.xml new file mode 100644 index 000000000..756fe1ccb --- /dev/null +++ b/src/main/config/checkstyle.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/config/pmd-exclude.properties b/src/main/config/pmd-exclude.properties new file mode 100644 index 000000000..03134d1af --- /dev/null +++ b/src/main/config/pmd-exclude.properties @@ -0,0 +1,14 @@ +# PMD exclusions with justifications +# Format: filepath=rule1,rule2 +# +# Example: +# net/openhft/chronicle/threads/LegacyWorker.java=AvoidSynchronizedAtMethodLevel +# THR-SB-320: Chronicle Threads relies on explicit threading primitives; suppress baseline style rules until follow-up THR-320. +net/openhft/chronicle/threads/.+\.java=DoNotUseThreads,MethodArgumentCouldBeFinal,LocalVariableCouldBeFinal,RedundantFieldInitializer,AvoidFieldNameMatchingMethodName,AvoidLiteralsInIfCondition,NullAssignment +# THR-SB-321: Internal monitoring utilities intentionally build verbose diagnostics; cleanup tracked in THR-321. +net/openhft/chronicle/threads/internal/.+\.java=DoNotUseThreads,MethodArgumentCouldBeFinal,LocalVariableCouldBeFinal,InsufficientStringBufferDeclaration,ConsecutiveAppendsShouldReuse,ConsecutiveLiteralAppends,AssignmentInOperand,UselessParentheses,CommentDefaultAccessModifier +# THR-SB-322: Legacy constructors use null sentinel assignments; tracked for refactor in THR-322. +net/openhft/chronicle/threads/EventGroup.java=NullAssignment +net/openhft/chronicle/threads/NamedThreadFactory.java=NullAssignment +net/openhft/chronicle/threads/VanillaEventLoop.java=NullAssignment +net/openhft/chronicle/threads/MediumEventLoop.java=AvoidInstantiatingObjectsInLoops diff --git a/src/main/config/pmd-ruleset.xml b/src/main/config/pmd-ruleset.xml new file mode 100644 index 000000000..d3d18cc63 --- /dev/null +++ b/src/main/config/pmd-ruleset.xml @@ -0,0 +1,12 @@ + + + + Baseline Chronicle rule selections used during the code-review profile. + + + + + diff --git a/src/main/config/spotbugs-exclude.xml b/src/main/config/spotbugs-exclude.xml new file mode 100644 index 000000000..c49d8373f --- /dev/null +++ b/src/main/config/spotbugs-exclude.xml @@ -0,0 +1,94 @@ + + + + + + THR-SB-310: LongPauser is single-thread owned; atomic rewrite scheduled in THR-310. + + + + + THR-SB-310: LongPauser metrics collected on owner thread; suppression maintained until pauser + refactor. + + + + + + THR-SB-310: Thread reference checked before use in practice; monitored under pauser refactor. + + + + + THR-SB-311: MilliPauser remains single-thread controlled; atomic upgrade tracked in THR-311. + + + + + THR-SB-311: Pauser statistics updated on owner thread only; refactor backlog THR-311. + + + + + THR-SB-311: Thread field set before pause; SpotBugs false positive tolerated until redesign. + + + + + THR-SB-312: Exposing worker thread required for diagnostics APIs; reviewed with platform team. + + + + + + THR-SB-312: Parent loop reference shared intentionally for handler lifecycle coordination. + + + + + THR-SB-312: Loop thread assigned before use; false positive documented. + + + + + THR-SB-313: Monitor loop must retain parent reference for monitoring callbacks. + + + + + THR-SB-314: BlockingEventLoop keeps parent reference for lifecycle coordination; replacement tracked in + THR-314. + + + + + + THR-SB-315: Constructor closes partial resources before rethrowing; reviewed with platform + maintainers. + + + + + + THR-SB-315: waitToStart deliberately rethrows TimeoutException to preserve legacy behaviour; follow-up + tracked with THR-315. + + + + + + THR-SB-401: Setter remains public to maintain backwards compatibility for DiskSpaceMonitor + configuration. + + + + + + THR-SB-402: Enum values() exposure accepted to preserve JVM generated API; callers obtain defensive copies. + + + + + THR-SB-403: Enum values() exposure required for diagnostic rendering; review tracked alongside renderer refit. + + diff --git a/src/main/adoc/decision-log.adoc b/src/main/docs/decision-log.adoc similarity index 69% rename from src/main/adoc/decision-log.adoc rename to src/main/docs/decision-log.adoc index 3a23806aa..68f125270 100644 --- a/src/main/adoc/decision-log.adoc +++ b/src/main/docs/decision-log.adoc @@ -20,30 +20,32 @@ Each decision record aims to provide context, the decision itself, its status, r * Decision Statement: ** Each `EventLoop` instance shall operate on a dedicated Java platform thread. ** All `EventHandler` instances registered with a particular `EventLoop` (excluding `BLOCKING` or `CONCURRENT` priority handlers which have their own threading model within an `EventGroup`) are executed serially by this single thread. -* **Alternatives Considered:** -** *Multi-threaded Event Loops:* -*** *Description:* Allow multiple handlers on the same loop to execute concurrently across a thread pool. -*** *Pros:* Potential for higher throughput on a single loop if handlers are independent and can truly run in parallel. -*** *Cons:* Requires handlers to be thread-safe, introduces complexity of locks or other concurrency primitives, makes latency less predictable due to potential contention, and increases the difficulty of reasoning about handler state. -** *Actor Model per Handler:* -*** *Description:* Each handler instance is an actor with its own queue and thread. -*** *Pros:* Strong isolation. -*** *Cons:* Higher resource consumption (threads, queues) and potentially higher message passing overhead compared to direct serialized invocation. -* **Rationale for Decision:** +Alternatives Considered :: +** _Multi-threaded Event Loops:_ +*** _Description:_ Allow multiple handlers on the same loop to execute concurrently across a thread pool. +*** _Pros:_ Potential for higher throughput on a single loop if handlers are independent and can truly run in parallel. +*** _Cons:_ Requires handlers to be thread-safe, introduces complexity of locks or other concurrency primitives, makes latency less predictable due to potential contention, and increases the difficulty of reasoning about handler state. +** _Actor Model per Handler:_ +*** _Description:_ Each handler instance is an actor with its own queue and thread. +*** _Pros:_ Strong isolation. +*** _Cons:_ Higher resource consumption (threads, queues) and potentially higher message passing overhead compared to direct serialized invocation. +Rationale for Decision :: ** This model significantly simplifies `EventHandler` implementation, as developers do not need to manage thread-safety within a handler's interaction with its own state or other handlers on the same loop. ** It promotes predictable, low-jitter latency by eliminating lock contention in the critical execution path of handlers. ** Aligns with the common "thread-per-core" pattern used in low-latency systems. -* **Impact & Consequences:** +Impact & Consequences :: ** Positive: ** Simplifies handler development and testing. ** Improves latency predictability for handlers on a given loop. ** Facilitates easier reasoning about application state managed by handlers. ** Negative: ** The throughput of a single `EventLoop` is limited by a single CPU core. -** A misbehaving (long-running or blocking) handler on a standard event loop can starve other handlers on the same loop. (This is mitigated by `EventGroup` allowing `BLOCKING` priority handlers to run in separate, dedicated thread pools). -* What are the trade-offs made? Single core throughput vs. simplicity and low-contention latency. -* **Notes/Links:** -*** This decision is fundamental to the design of `VanillaEventLoop` and `MediumEventLoop`. +** A misbehaving (long-running or blocking) handler on a standard event loop can starve other handlers on the same loop. +(This is mitigated by `EventGroup` allowing `BLOCKING` priority handlers to run in separate, dedicated thread pools). +* What are the trade-offs made? +Single core throughput vs. simplicity and low-contention latency. +Notes/Links :: +** This decision is fundamental to the design of `VanillaEventLoop` and `MediumEventLoop`. === THR-NF-P-002: Default to Busy-Spin Pauser for Latency-Critical "Fast Threads" @@ -54,30 +56,32 @@ Each decision record aims to provide context, the decision itself, its status, r * Decision Statement: ** `EventLoop` instances configured for the highest performance (e.g., using `PauserMode.BUSY` or `PauserMode.TIMED_BUSY`) shall employ a busy-spinning (or near busy-spinning) `Pauser` strategy by default. ** Alternative pauser modes (`YIELDING`, `BALANCED`, `SLEEPY`, `MILLI`) remain available and are recommended for less latency-sensitive loops or systems with CPU core constraints. -* **Alternatives Considered:** -** *Yielding Pauser as Default:* -*** *Description:* `Thread.yield()` immediately when idle. -*** *Pros:* More CPU-friendly than busy-spinning. -*** *Cons:* Introduces higher and less predictable wakeup latency. -** *Sleeping Pauser as Default:* -*** *Description:* `LockSupport.parkNanos()` or `Thread.sleep()` immediately when idle. -*** *Pros:* Lowest CPU usage when idle. -*** *Cons:* Highest wakeup latency. -** *Adaptive Pauser (e.g., `BALANCED`) as Default for all:* -*** *Description:* Uses a mix of spinning, yielding, and sleeping. -*** *Pros:* Good balance for general use cases. -*** *Cons:* Not the absolute lowest latency for highly optimized fast threads compared to pure busy-spinning. -* **Rationale for Decision:** +Alternatives Considered :: +** _Yielding Pauser as Default:_ +*** _Description:_ `Thread.yield()` immediately when idle. +*** _Pros:_ More CPU-friendly than busy-spinning. +*** _Cons:_ Introduces higher and less predictable wakeup latency. +** _Sleeping Pauser as Default:_ +*** _Description:_ `LockSupport.parkNanos()` or `Thread.sleep()` immediately when idle. +*** _Pros:_ Lowest CPU usage when idle. +*** _Cons:_ Highest wakeup latency. +** _Adaptive Pauser (e.g., `BALANCED`) as Default for all:_ +*** _Description:_ Uses a mix of spinning, yielding, and sleeping. +*** _Pros:_ Good balance for general use cases. +*** _Cons:_ Not the absolute lowest latency for highly optimized fast threads compared to pure busy-spinning. +Rationale for Decision :: ** Busy-spinning keeps the thread's cache hot and avoids the overhead of context switching and OS scheduler-induced delays, leading to the lowest possible reaction time when an event becomes available. -* **Impact & Consequences:** +Impact & Consequences :: ** Positive: ** Achieves minimal event processing latency (p99.99 and tail latencies) for designated fast threads. ** Predictable entry into event handler logic once an event is detected. ** Negative: -** Consumes 100% of a CPU core, even when idle. This necessitates careful system configuration, including CPU isolation (`isolcpus`) and ensuring enough cores are available for other system tasks and less critical threads. +** Consumes 100% of a CPU core, even when idle. +This necessitates careful system configuration, including CPU isolation (`isolcpus`) and ensuring enough cores are available for other system tasks and less critical threads. ** If not managed properly (e.g., too many busy-spinning threads for available isolated cores), it can degrade overall system performance. -* What are the trade-offs made? Lowest latency vs. high CPU consumption and need for careful system tuning. -* **Notes/Links:** +* What are the trade-offs made? +Lowest latency vs. high CPU consumption and need for careful system tuning. +Notes/Links :: *** See `Pauser.java`, `BusyPauser.java`, and `PauserMode.java`. *** Documented recommendation in `README.adoc` and `project-requirements.adoc` (R4-02). @@ -91,28 +95,30 @@ Each decision record aims to provide context, the decision itself, its status, r ** A background monitoring singleton (`DiskSpaceMonitor`) shall be provided within Chronicle Threads. ** This monitor will periodically check disk usage for paths associated with Chronicle components (e.g., queue directories when they are initialized). ** Warnings shall be logged via a `NotifyDiskLow` service (defaulting to `NotifyDiskLowLogWarn`) when free disk space drops below a configurable percentage threshold (system property: `chronicle.disk.monitor.threshold.percent`). -* **Alternatives Considered:** -** *No built-in monitoring:* -*** *Description:* Require users to rely solely on external, system-level disk monitoring tools. -*** *Pros:* Simplifies the library. -*** *Cons:* Less integrated; users might overlook setting up adequate external monitoring tailored to Chronicle's usage patterns. -** *More Aggressive Actions:* -*** *Description:* e.g., halt queue appenders if disk space is critically low. -*** *Pros:* Could prevent further writes that might lead to immediate crashes. -*** *Cons:* May be too intrusive for a general-purpose library; such policies are better implemented at the application level. The library's role is primarily to inform. -* **Rationale for Decision:** +Alternatives Considered :: +** _No built-in monitoring:_ +*** _Description:_ Require users to rely solely on external, system-level disk monitoring tools. +*** _Pros:_ Simplifies the library. +*** _Cons:_ Less integrated; users might overlook setting up adequate external monitoring tailored to Chronicle's usage patterns. +** _More Aggressive Actions:_ +*** _Description:_ e.g., halt queue appenders if disk space is critically low. +*** _Pros:_ Could prevent further writes that might lead to immediate crashes. +*** _Cons:_ May be too intrusive for a general-purpose library; such policies are better implemented at the application level. +The library's role is primarily to inform. +Rationale for Decision :: ** Provides a built-in, proactive layer of defense that is easy to enable. ** Logging warnings is a non-intrusive way to alert operations without unilaterally halting application functionality. ** The `ServiceLoader` mechanism for `NotifyDiskLow` allows for custom notification actions if needed. -* **Impact & Consequences:** +Impact & Consequences :: ** Positive: ** Operations teams receive early warnings of potential storage exhaustion, allowing for proactive intervention. ** Reduces the risk of unexpected application failures due to full disks. ** Negative: ** The monitor consumes minimal system resources (one background thread, periodic I/O for disk checks). ** Effectiveness relies on logs being actively monitored or a custom `NotifyDiskLow` service being implemented for more active notifications. -* What are the trade-offs made? Built-in convenience and basic safety vs. reliance on external monitoring for comprehensive operational alerting. -* **Notes/Links:** +* What are the trade-offs made? +Built-in convenience and basic safety vs. reliance on external monitoring for comprehensive operational alerting. +Notes/Links :: *** See `DiskSpaceMonitor.java`, `NotifyDiskLow.java`, `NotifyDiskLowLogWarn.java`. *** Key system properties: `chronicle.disk.monitor.disable`, `chronicle.disk.monitor.threshold.percent`. @@ -125,20 +131,20 @@ Each decision record aims to provide context, the decision itself, its status, r * Decision Statement: ** All functional requirements (in `project-requirements.adoc`) and architectural decisions (in this log) shall use identifiers prefixed with `THR-` followed by a tag from the Nine-Box taxonomy (e.g., `FN`, `NF-P`, `OPS`, `DOC`) and a sequential number (e.g., `THR-FN-001`). ** The specific Nine-Box tag definitions and usage guidelines are maintained in the project's `AGENTS.md` document. -* **Alternatives Considered:** -** *Simple Sequential Numbering (e.g., REQ-001, DEC-001):* -*** *Description:* Basic sequential IDs. -*** *Pros:* Very simple to implement. -*** *Cons:* Provides no information about the type or domain of the item from its ID. -** *Custom Categorization Scheme:* -*** *Description:* Develop a project-specific set of categories. -*** *Pros:* Could be perfectly tailored. -*** *Cons:* Requires effort to define and maintain; less transferable knowledge if team members work on other projects using different schemes. -* **Rationale for Decision:** +Alternatives Considered :: +** _Simple Sequential Numbering (e.g., REQ-001, DEC-001):_ +*** _Description:_ Basic sequential IDs. +*** _Pros:_ Very simple to implement. +*** _Cons:_ Provides no information about the type or domain of the item from its ID. +** _Custom Categorization Scheme:_ +*** _Description:_ Develop a project-specific set of categories. +*** _Pros:_ Could be perfectly tailored. +*** _Cons:_ Requires effort to define and maintain; less transferable knowledge if team members work on other projects using different schemes. +Rationale for Decision :: ** The Nine-Box taxonomy (referenced from `AGENTS.md`) provides a pre-defined, reasonably comprehensive set of categories that are broadly applicable to software development artifacts. ** Using this existing scheme promotes consistency if it's adopted across multiple Chronicle Software projects. ** Tags in IDs offer immediate insight into the item's domain. -* **Impact & Consequences:** +Impact & Consequences :: ** Positive: ** Improved traceability between requirements, decisions, tests, and potentially code. ** Easier for team members to understand the context of an identified item quickly. @@ -146,6 +152,7 @@ Each decision record aims to provide context, the decision itself, its status, r ** Negative: ** Requires team members to be familiar with the Nine-Box taxonomy and apply it consistently. ** Initial setup of the scheme and guidelines in `AGENTS.md`. -* What are the trade-offs made? Richer categorization and traceability vs. slight learning curve for the taxonomy. -* **Notes/Links:** +* What are the trade-offs made? +Richer categorization and traceability vs. slight learning curve for the taxonomy. +Notes/Links :: *** The Nine-Box taxonomy details are in `AGENTS.md`. diff --git a/src/main/docs/project-requirements.adoc b/src/main/docs/project-requirements.adoc new file mode 100644 index 000000000..7d533e034 --- /dev/null +++ b/src/main/docs/project-requirements.adoc @@ -0,0 +1,233 @@ += Chronicle Threads – Functional Requirements Specification +:revnumber: 1.0 +:revdate: 2025-05-25 +:sectnums: +:toc: +:source-highlighter: rouge +:lang: en-GB + +== Purpose and Scope + +This document specifies the _functional requirements_ for the _Chronicle Threads_ library (OpenHFT / Chronicle Software). +It is aimed at architects, developers, and performance engineers who intend to use Chronicle Threads as the execution engine for ultra-low-latency, event-driven Java systems. + +_Out of scope_ are: +* Detailed Non-Functional Requirement (NFR) specifications beyond the key performance targets identified in the <> section. +* Licensing and commercial support details. +* The detailed product roadmap (though the <> section outlines potential future enhancements). + +== Definitions + +[cols="1,3"] +|=== +|_Term_ |_Meaning_ + +|_EventLoop_ |A single-threaded loop that repeatedly invokes `EventHandler` instances. +|_EventHandler_ |Application-provided component that implements `action()`; executed by an `EventLoop`. +|_EventGroup_ |A container that manages one or more `EventLoop`s, potentially including a dedicated monitor loop. +|_Pauser_ |Strategy object that controls how an idle loop waits (e.g., busy-spin, yield, sleep). +|_Fast Thread_ |A thread—usually pinned to an isolated CPU core—running a latency-critical event loop. +|_Hot Path_ / _Fast Path_ |The code execution path that is performance-critical and frequently executed, where low latency and minimal overhead are paramount. +|_Loop-Block Monitor_ |Background handler that measures `EventHandler` run-time and logs outliers exceeding a configured threshold. +|_NUMA_ |Non-Uniform Memory Access; a memory architecture where memory access time depends on the memory location relative to a processor. +|_Chronicle Affinity_ |A library used for managing thread affinity, allowing threads to be pinned to specific CPU cores. +|=== + +== Overall Architectural Goals + +* Minimise *end-to-end latency* and *jitter* for event processing. +* Provide a *deterministic single-threaded* execution model for event handlers within an `EventLoop`, removing the need for locks in the hot path of handler logic. +* Allow *fine-grained control* of the latency versus CPU consumption trade-off. +* Integrate effectively with other Chronicle components (e.g., Chronicle Queue, Chronicle Map, Chronicle Wire, Chronicle Services, Chronicle Tune). + +== High-Level Functional Requirements + +=== Event‐Loop Lifecycle and Configuration + +. _Creation and Configuration_ +_THR-FN-001_ The library SHALL provide builder APIs (e.g., `EventLoopBuilder`, `EventGroupBuilder`) for fluent and immutable configuration of event loops and groups. +. _Start/Stop Operations_ +_THR-FN-002_ An `EventLoop` or `EventGroup` SHALL support idempotent `start()` and graceful `close()` operations. +A graceful `close()` implies that active handlers are allowed to complete their current action, and associated resources are released. +. _Non-Restartable Loops_ +_THR-FN-003_ Attempting to `start()` an `EventLoop` or `EventGroup` that has already been `close()`d SHALL be rejected or have no effect, as loops are not designed to be restartable. + +=== Handler Management + +. _Dynamic Registration_ +_THR-FN-004_ Clients SHALL be able to add an `EventHandler` to a running `EventGroup` at runtime. +. _Handler Priority_ +_THR-FN-005_ Each `EventHandler` SHALL declare a `HandlerPriority`. +The system SHALL support a range of priorities influencing execution order and/or loop assignment. +(Refer to `net.openhft.chronicle.core.threads.HandlerPriority` enum for the exhaustive list of priorities, e.g., `HIGH`, `MEDIUM`, `LOW`, `TIMER`, `BLOCKING`, `REPLICATION`, `CONCURRENT`, `MONITOR`, `DAEMON`). +. _Execution Contract_ +_THR-FN-006_ An `EventHandler`'s `action()` method MUST be invoked serially on the `EventLoop`'s dedicated thread. +_THR-FN-007_ The `action()` method MUST return a boolean: `true` if useful work was done (suggesting the handler may have more immediate work), `false` otherwise. +. _Self-Deregistration_ +_THR-FN-008_ An `EventHandler` MAY request its own deregistration from the `EventLoop` by throwing `net.openhft.chronicle.core.threads.InvalidEventHandlerException`. +. _Error Isolation and Reporting_ +_THR-NF-O-009_ Unchecked exceptions thrown by an `EventHandler`'s `action()` method SHALL NOT terminate the `EventLoop` itself. +The offending handler SHALL be removed, and the error SHALL be reported using the standard `net.openhft.chronicle.core.Jvm.warn()` logging mechanism by default. + +=== Idle Strategy (Pauser) + +. _Supported Pauser Modes_ +_THR-FN-010_ The library SHALL ship with a set of standard `Pauser` strategies, configurable via `PauserMode` enum values, including at least: `BUSY`, `TIMED_BUSY`, `YIELDING`, `BALANCED`, `MILLI`, and `SLEEPY`. +(Refer to `net.openhft.chronicle.threads.PauserMode.java` and `README.adoc` for details on each mode). +. _Custom Pauser Pluggability_ +_THR-FN-011_ Applications SHALL be able to supply custom `Pauser` implementations via programmatic configuration (e.g., through builder APIs like `EventGroupBuilder.withPauser(Pauser customPauser)`). +. _Adaptive Back-off Parameterisation_ +_THR-FN-012_ Adaptive pausers (such as `LongPauser`, which underpins modes like `BALANCED` and `SLEEPY`) SHALL allow parameterisation of their back-off behaviour, including aspects like minimum busy-spin duration, yield duration, and minimum/maximum sleep times. +. _TimingPauser Support_ +_THR-NF-O-013_ `TimingPauser` implementations (e.g., `LongPauser`, `BusyTimedPauser`) SHALL expose pause-related metrics (e.g., total time paused via `timePaused()`, pause count via `countPaused()`) and SHALL optionally throw `java.util.concurrent.TimeoutException` when a configured timeout duration is exceeded during a timed pause. +. _Zero-Allocation Hot Path_ +_THR-NF-P-014_ The hot path methods `Pauser.pause()` and `Pauser.reset()` for built-in pausers SHALL NOT allocate heap objects. + +=== Thread Affinity and CPU Isolation + +. _CPU Affinity API_ +_THR-FN-015_ The library SHALL provide mechanisms to bind `EventLoop` threads to specific CPU cores, utilizing Chronicle Affinity. +This SHALL be configurable via builder APIs (e.g., `EventGroupBuilder.withBinding(String affinity)`). +. _CPU Isolation Guidance_ +_THR-DOC-016_ Documentation (`README.adoc`) SHALL recommend OS-level isolation of CPU cores (e.g., using `isolcpus` on Linux) for `EventLoop` threads when latency-sensitive pausers (like `PauserMode.BUSY` or `PauserMode.TIMED_BUSY`) are used, to minimize jitter. +. _NUMA Awareness_ +_THR-FN-017_ Builders SHOULD allow configuration that facilitates pinning `EventLoop` threads to specific NUMA nodes. +This is typically achieved via the `binding` string syntax provided to Chronicle Affinity, which can specify core layouts respecting NUMA topology. + +=== Monitoring and Diagnostics + +. _Loop Block Monitoring_ +_THR-NF-O-018_ For an `EventGroup`, a dedicated monitor loop (e.g., `MonitorEventLoop`) SHALL, by default, measure the wall-clock duration of `EventHandler` invocations on other event loops within the group. +_THR-NF-O-019_ If an `EventHandler`'s `action()` method duration exceeds a configurable threshold (defaulting to a value specified by `loop.block.threshold.ns`), the framework SHALL capture and log a stack trace of the event loop thread executing that handler. +. _Monitoring Configuration Toggles_ +_THR-OPS-020_ Loop block monitoring MAY be disabled globally via a system property (e.g., `disableLoopBlockMonitor=true`). +The monitoring interval SHALL also be configurable (e.g., `MONITOR_INTERVAL_MS`). +. _Pauser Metrics Accessibility_ +_THR-NF-O-021_ Key metrics from `Pauser` instances, such as `timePaused()` and `countPaused()`, SHALL be programmatically accessible. +Implementations of `PauserMonitorFactory` MAY provide handlers to monitor and log these. +. _Low-Overhead Monitoring_ +_THR-NF-P-022_ When all `EventHandler` invocations are within their execution thresholds, the loop block monitoring mechanism MUST impose negligible overhead (target < 1 us overhead per monitored loop per second, excluding logging actions if a threshold is breached). + +=== Configuration and Deployment + +. _System Property Overrides_ +_THR-OPS-023_ Default behaviours and parameters (e.g., pauser modes, monitor intervals, logging thresholds) SHALL be overridable via JVM system properties. +(Refer to `systemProperties.adoc` for a comprehensive list; Appendix B provides examples). +. _Programmatic Configuration Precedence_ +_THR-OPS-024_ Programmatic configurations provided via builder APIs SHALL take precedence over global system property settings. +. _Graceful JVM Shutdown Hook_ +_THR-OPS-025_ An `EventGroup` instance, when configured appropriately (e.g., via `((net.openhft.chronicle.core.io.AbstractCloseable) eventGroup).addShutdownHook(true)`), SHALL attempt to `close()` automatically on JVM exit. +. _JDK Compatibility_ +_THR-NF-O-026_ Chronicle Threads SHALL run on Java 11 LTS or newer. +The library SHALL default to using platform threads. +While aiming for compatibility with JDK Project Loom (virtual threads) for suitable use cases (e.g., blocking handlers not requiring core affinity), full support and affinity guarantees with virtual threads depend on JDK evolution and are subject to considerations detailed in the <> section. + +[[key-performance-targets]] +== Key Performance Targets + +These non-functional targets guide the design and optimization of Chronicle Threads for ultra-low-latency scenarios. +They are primarily applicable when using performance-oriented pausers (e.g., `BUSY`) on suitably configured systems (e.g., with isolated cores). + +_THR-NF-P-027_ *Latency:* Single-hop message processing through an event handler SHALL target <= 10 us at the 99.99th percentile on commodity x86_64 hardware with a busy pauser and isolated cores. +_THR-NF-P-028_ *Jitter:* Peak-to-peak variation in handler execution time SHALL target <= 2 us under steady load conditions for well-behaved handlers. +_THR-NF-P-029_ *Throughput:* A single "fast core" `EventLoop` SHALL be capable of processing >= 5 million simple (e.g., 64-byte payload) events per second. +_THR-NF-P-030_ *Heap Allocation:* In the hot path of event processing (i.e., within the `EventLoop` and `EventHandler.action()` calls for common use cases), heap allocation SHALL target <= 0.1 Bytes per event on average. +Pauser hot paths are covered by THR-NF-P-014. +_THR-NF-P-031_ *CPU Utilisation:* +* `PauserMode.BUSY` SHALL consume 100% of its assigned (and ideally isolated) CPU core. +* Adaptive pauser modes (e.g., `BALANCED`, `SLEEPY`) SHALL reduce CPU consumption significantly (e.g., target < 20%) when the event loop is idle. + +== Documentation, Testing, and Traceability + +[cols="1,4,3",options="header"] +|=== +|ID |Requirement |Artefact(s) +|THR-DOC-032 |Publish an architecture overview that illustrates loop composition, handler routing, and affinity guidance, keeping it aligned with the functional catalogue. |link:thread-architecture-overview.adoc[thread-architecture-overview.adoc] +|THR-DOC-033 |Maintain an operational controls playbook detailing CPU isolation, monitoring thresholds, and configuration precedence so operators can enforce safe defaults. |link:thread-operational-controls.adoc[thread-operational-controls.adoc] +|THR-DOC-034 |Record security considerations for handler admission, affinity, telemetry integrity, and dependency posture, updating the review after material changes. |link:thread-security-review.adoc[thread-security-review.adoc] +|THR-DOC-035 |Provide a thread-safety guide that explains confinement rules, hand-off patterns, and testing practices for event loop handlers. |link:thread-thread-safety-guide.adoc[thread-thread-safety-guide.adoc] +|THR-TEST-036 |Automate benchmark and soak tests that demonstrate compliance with latency, jitter, throughput, and allocation targets. |link:thread-performance-targets.adoc[thread-performance-targets.adoc] +|=== + +== Use-Case Scenarios + +=== Matching Engine + +A financial matching engine processes incoming orders and market data. +_Multiple_ `EventHandler` instances (e.g., for order book management, risk checks, trade execution) share a `HIGH` priority `EventLoop` pinned to an isolated CPU core (Core 2). +(Illustrates: THR-FN-005, THR-FN-006, THR-FN-015, THR-NF-P-027) A `MEDIUM` priority `EventLoop` on a separate core (Core 3) handles journalling of trades and significant events to Chronicle Queue. +(Illustrates: THR-FN-005, THR-FN-015) A `MONITOR` loop, possibly on a non-isolated core, supervises both application loops. +(Illustrates: THR-NF-O-018) + +=== Bursty Telemetry Ingestion + +An `EventLoop` configured with a `PauserMode.BALANCED` ingests UDP packets containing telemetry data. +The `EventHandler` parses these packets (e.g., using Chronicle Wire) and forwards them to a Chronicle Queue for downstream processing. +During off-peak hours, CPU usage for this loop drops significantly (e.g., below 5%) due to the pauser's adaptive back-off. +During bursts, it processes events with low latency. +(Illustrates: THR-FN-010, THR-FN-012, THR-NF-P-031) + +== References + +* Chronicle Threads `README.adoc` (Provides overview, usage examples, and pauser details) +* `systemProperties.adoc` (Comprehensive list of configurable JVM system properties) +* `net.openhft.chronicle.core.threads.HandlerPriority` Javadoc (Definitive list of handler priorities) +* `net.openhft.chronicle.threads.PauserMode` Javadoc (Definitive list of pauser modes) +* Chronicle Affinity library documentation (For details on CPU binding syntax and capabilities) +* `thread-architecture-overview.adoc` (Runtime topology) +* `thread-operational-controls.adoc` (Operational safeguards) +* `thread-performance-targets.adoc` (Benchmark methodology) +* `thread-security-review.adoc` (Security posture) +* `thread-thread-safety-guide.adoc` (Confinement practices) + +[[open-issues]] +== Open Issues / Future Enhancements + +This section lists areas identified for potential future development or requiring further investigation. +They are not committed functional requirements for the current version. + +* Support for *carrier-thread reuse* with JDK virtual threads while retaining affinity guarantees where possible. +* First-class asynchronous I/O helper components to better integrate frameworks like Netty or `java.nio.channels` directly with `EventLoop`s. +* Live reconfiguration of `Pauser` parameters via JMX or a similar management interface. +* Enhanced built-in metrics publication mechanisms beyond basic pauser counters and loop-block logs. + +== Appendices + +=== A Builder Example + +[source,java] +---- +EventGroup eg = EventGroup.builder() + .withName("MatchingEngineGroup") // Sets the base name for the EventGroup and its child loops + .withLoopCount(2) // Example: might influence number of certain types of loops if applicable + .withPauserMode(PauserMode.BUSY) // Sets default pauser for core loops + .withPriorities(EnumSet.of(HandlerPriority.HIGH, HandlerPriority.MEDIUM, HandlerPriority.MONITOR)) // Specify which handler priorities this group will support + .build(); + +// Add application-specific handlers +eg.addHandler(new MatchingEngineOrderHandler()); // Assuming this implements EventHandler +eg.addHandler(new JournalWriterHandler()); // Assuming this implements EventHandler + +eg.start(); + +// finally +eg.close(); +---- + +=== B System Properties Quick Reference + +This is a non-exhaustive list of key system properties. +For a comprehensive list, refer to the `systemProperties.adoc` document. + +[cols="1,3"] +|=== +|_Property_ |_Effect / Default (Illustrative)_ + +|`pauserMode` |Global override for default pauser selection (e.g., `busy`, `balanced`). Used if not specified by builder. +|`loop.block.threshold.ns` |Nanoseconds before a handler invocation is flagged as a block (Default: 100,000,000 ns = 100 ms). +|`MONITOR_INTERVAL_MS` |Sampling interval for the monitor loop (Default: 100 ms). +|`disableLoopBlockMonitor` |Set to `true` to disable loop block monitoring (Default: `false`). +|`eventGroup.conc.threads` | Default number of threads for `CONCURRENT` priority handlers (Default: Varies, e.g., CPU cores / 4). +|`chronicle.disk.monitor.disable` | Set to `true` to disable the disk space monitor (Default: `false`). +|`chronicle.disk.monitor.threshold.percent` | Disk usage percentage above which warnings are issued (Default: 5%). +|=== diff --git a/src/main/docs/thread-architecture-overview.adoc b/src/main/docs/thread-architecture-overview.adoc new file mode 100644 index 000000000..3d6b91f5e --- /dev/null +++ b/src/main/docs/thread-architecture-overview.adoc @@ -0,0 +1,95 @@ += Chronicle Threads Architecture Overview +:toc: +:sectnums: +:lang: en-GB + +== Purpose + +This guide explains how Chronicle Threads composes event loops, handlers, pausers, and monitoring into a cohesive runtime so that engineers can reason about placement, affinity, and operational behaviour. +It complements the functional catalogue in `project-requirements.adoc` and provides concrete design cues for solution architects. + +== Event Loop Topologies + +Chronicle Threads organises work into named `EventLoop` instances that the `EventGroup` manages (THR-FN-001). +Each loop is single-threaded for handler execution (THR-FN-006) and is categorised by handler priority (THR-FN-005). + +.... + EventGroup + | + +-- CoreLoop[HIGH|MEDIUM] ---> fast path handlers (trading logic, matching) + | + +-- BlockingPool[BLOCKING] --> dedicated threads for I/O or storage waits + | + +-- TimerLoop[TIMER] ------> scheduled maintenance and time-based work + | + +-- MonitorLoop[MONITOR] -> observes loop-block latency and pauser metrics +.... + +Handlers attach to the loop whose priority matches their declared `HandlerPriority`. +An `EventGroup` materialises blocking and monitor loops only when required. +Applications can deploy multiple `EventGroup` instances in the same JVM to isolate subsystems whilst sharing pauser implementations. + +== Handler Lifecycle and Serial Execution + +Handlers are added at runtime via `EventGroup.addHandler()` (THR-FN-004). +The loop invokes each handler serially, ensuring stateful logic can remain lock-free (THR-FN-006). +The handler signals its progress via the boolean return value of `action()` (THR-FN-007). +Self-removal uses `InvalidEventHandlerException` (THR-FN-008); the loop removes the handler, logs through the standard `Jvm` channel, and continues running (THR-NF-O-009). + +Handlers should bound their execution time so that monitor loops can flag outliers reliably (THR-NF-O-018). +Long-running work belongs on the `BLOCKING` priority where independent threads handle it. +When reconfiguring a live loop, call `EventLoop.addHandler()` on the owning loop thread or rely on the concurrency-safe wrappers provided by `EventGroup`. + +== Pauser Strategy and Scheduler Interaction + +Pausers implement the idle strategy for each loop and are configured via builders or per-loop overrides (THR-FN-010, THR-FN-011). +Adaptive pausers expose tuning parameters that balance busy-spin and sleeping phases (THR-FN-012) while exposing metrics for observability (THR-NF-O-013, THR-NF-O-021). + +* `BUSY` / `TIMED_BUSY`: Bind to isolated cores, targeting nanosecond wake-up latency (THR-DOC-016). +* `BALANCED` / `SLEEPY`: Combine spin, yield, and park for mixed workloads. +* Custom: Provide a bespoke `Pauser` for domain-specific throttling. + +Hot paths avoid allocations (THR-NF-P-014) so a pauser change cannot introduce garbage. +Each loop records the time spent paused, supporting utilisation diagnostics. + +== Affinity and NUMA Alignment + +Affinity strings supplied via builders control how loops bind to hardware threads (THR-FN-015). +They accept the Chronicle Affinity syntax, including NUMA-aware layouts (THR-FN-017). +Example: + +---- +EventGroup eg = EventGroup.builder() + .withName("risk-eg") + .withBinding("0,2-3") + .build(); +---- + +* `0` binds the primary high-priority loop to core 0. +* `2-3` pins additional loops (e.g., MONITOR or BLOCKING) across cores 2 and 3. + +When multiple `EventGroup` instances coexist, coordinate bindings to avoid core contention. +Document selected affinities alongside deployment manifests so operators can validate CPU isolation. + +== Monitoring Plane + +Each `EventGroup` provisions a monitor loop that samples execution times and resets pausers at configurable intervals (THR-NF-O-018, THR-NF-O-019, THR-OPS-020). +The monitor loop: + +* Measures handler invocation duration, logging stack traces for breaches. +* Publishes pauser metrics through configured `PauserMonitorFactory` hooks. +* Responds to system properties that disable or tune monitoring (THR-OPS-023). + +The monitoring loop is not latency-critical but must keep pace with the core loops to avoid stale diagnostics. +Ensure JVM logging levels capture WARN messages from monitor handlers in production. + +== Integration Touchpoints + +Chronicle Threads commonly underpins Chronicle Queue tailers, Chronicle Map maintenance tasks, and application-specific pipelines. +When integrating: + +* Use `net.openhft.chronicle.core.io.Closeable` semantics to align handler lifecycle with queue appenders or tailers. +* Combine telemetry exports with the monitor loop to funnel utilisation metrics to the estate-wide monitoring system. +* Align handler priorities with data criticality so that core loops handle order flow while auxiliary loops manage persistence, replay, or housekeeping. + +Refer to `README.adoc` for code-level examples and to the operational controls document for deployment-time safeguards. diff --git a/src/main/docs/thread-operational-controls.adoc b/src/main/docs/thread-operational-controls.adoc new file mode 100644 index 000000000..9d8626b41 --- /dev/null +++ b/src/main/docs/thread-operational-controls.adoc @@ -0,0 +1,94 @@ += Chronicle Threads Operational Controls +:toc: +:sectnums: +:lang: en-GB + +== CPU Isolation and Affinity Governance + +Why :: +Latency-sensitive handlers rely on predictable scheduling and cache residency. + +Core controls :: +* Reserve dedicated CPU cores for loops using busy pausers, aligning with documented recommendations (THR-DOC-016). +* Validate runtime affinity strings against estate topology before deployment (THR-FN-015, THR-FN-017). +* Record the chosen affinity mapping in run-books so support engineers can confirm compliance during incident response. + +Review hot-spots :: +* K8s or container orchestrators that may reassign cores. +* BIOS or hypervisor changes that alter NUMA layout. +* Third-party tooling that repins threads (profilers, debuggers). + +== Loop-Block Monitoring and Alerting + +Why :: +A stalled handler compromises all work on its loop and introduces systemic jitter. + +Core controls :: +* Keep the monitor loop enabled in production to enforce execution thresholds (THR-NF-O-018, THR-NF-O-019). +* Tune `loop.block.threshold.ns` and `MONITOR_INTERVAL_MS` via system properties to reflect acceptable tail latency (THR-OPS-023, THR-OPS-024). +* Integrate `PauserMonitorFactory` outputs with telemetry collectors so SLO breaches surface quickly (THR-NF-O-021). + +Review hot-spots :: +* Handlers that call out to external services. +* Contended locks inside business logic. +* JVM safepoint pauses observable as correlated spikes across all loops. + +== Startup, Shutdown, and Recovery + +Why :: +Predictable lifecycle management prevents resource leaks and eases maintenance. + +Core controls :: +* Configure shutdown hooks or explicit close ordering so loops stop gracefully and relinquish resources (THR-FN-002, THR-OPS-025). +* Use builder precedence rules to override unsuitable host-wide defaults (THR-OPS-024). +* Include loop topology and handler binding in operational documentation to guide failover drills. + +Review hot-spots :: +* Mutable static state shared across handlers that survives restart. +* Incomplete handler deregistration causing repeated warnings during shutdown. +* JVM exit sequences where native resources must release before process termination. + +== Configuration Hygiene + +Why :: +Misconfiguration can disable safety features or erode performance targets. + +Core controls :: +* Maintain an allow-listed set of Chronicle Threads system properties and validate them in CI pipelines. +* Version control default builder profiles for each environment (development, certification, production) and peer review changes. +* Capture pauser and monitor settings in infrastructure-as-code artefacts to avoid snowflake deployments. + +Review hot-spots :: +* Ad-hoc overrides applied via command-line flags. +* Legacy scripts that pre-date the Nine-Box taxonomy and omit traceability IDs. +* Environment variable templating that truncates affinity strings. + +== Telemetry and Observability + +Why :: +Workload visibility enables tuning and rapid diagnosis. + +Core controls :: +* Export pauser and loop-block metrics to the organisation-wide metrics pipeline (e.g., Prometheus, Graphite). +* Correlate Chronicle Threads metrics with downstream components (Queues, Maps) to contextualise latency spikes. +* Ensure monitor-loop warnings are promoted to actionable alerts rather than suppressed in logs. + +Review hot-spots :: +* Handlers that bypass standard logging frameworks. +* Log rotation policies that discard stack traces before investigation. +* Telemetry exporters that share threads with latency-sensitive loops. + +== Change Management + +Why :: +Threading behaviour influences end-to-end latency; uncontrolled change increases risk. + +Core controls :: +* Pair configuration modifications with updated documentation and automated tests (THR-OPS-023). +* Track requirement IDs (e.g., THR-NF-P-027) in change tickets so reviewers can verify continued compliance. +* Simulate workload impact in a staging environment whenever pausers, affinities, or monitor thresholds change. + +Review hot-spots :: +* Hot fixes applied directly to production nodes. +* Divergent configuration between active-active sites. +* Missing rollback plans for affinity or pauser adjustments. diff --git a/src/main/docs/thread-performance-targets.adoc b/src/main/docs/thread-performance-targets.adoc new file mode 100644 index 000000000..4d823f05d --- /dev/null +++ b/src/main/docs/thread-performance-targets.adoc @@ -0,0 +1,69 @@ += Chronicle Threads Performance Targets +:toc: +:sectnums: +:lang: en-GB + +== Scope + +This document enumerates the latency, jitter, throughput, and allocation targets for Chronicle Threads and describes how teams must measure and report them. +It elaborates the non-functional requirements captured in `project-requirements.adoc` (THR-NF-P-014 through THR-NF-P-031). + +== Reference Hardware Profile + +Baseline :: +* Dual-socket x86_64 server, 3.2 GHz or faster, Turbo disabled. +* 64 GiB RAM, uniform memory access within a socket. +* Linux kernel 5.15 or newer with `isolcpus`, `nohz_full`, and `rcu_nocbs` tuned for fast cores. +* OpenJDK 21 LTS, G1 GC, `-XX:+UseNUMA`, `-XX:+AlwaysPreTouch`. + +Variations :: +* ARM64 hosts must document deviations from the x86 baseline and retune thresholds accordingly. +* Virtualised environments require an additional jitter budget that is recorded alongside benchmark artefacts. + +== Target Matrix + +[cols="2,3,3",options="header"] +|=== +|Requirement |Target |Measurement Notes +|THR-NF-P-027 (Latency) |<= 10 microseconds at 99.99 percentile for single-hop handler runs |Profiling harness schedules 10 million iterations with a busy pauser and isolated core. +|THR-NF-P-028 (Jitter) |<= 2 microseconds peak-to-peak jitter under steady load |Continuous histogram per handler, sampled via monitor loop over 15 minute windows. +|THR-NF-P-029 (Throughput) |>= 5 million 64-byte events per second on a fast loop |Benchmark harness dispatches fixed-size payloads, recording sustained processing rate. +|THR-NF-P-030 (Heap Allocation) |<= 0.1 Bytes per event averaged across handlers |Java Flight Recorder or allocation profiler attached during workload replay. +|THR-NF-P-014 (Pauser Hot Path) |0 allocations in `Pauser.pause()` / `reset()` |Unit tests instrumented with allocation counters; CI gate fails on non-zero heap activity. +|THR-NF-P-031 (CPU Utilisation) |Loop CPU utilisation tracks input rate; idle loops drop below 5 percent |Derived from pauser metrics (`timePaused`, `countPaused`); reported via telemetry dashboards. +|=== + +== Measurement Methodology + +Workload Selection :: +* Use representative handlers (queue tailer, order matching micro-benchmark, timed maintenance task). +* Include at least one blocking handler routed to the `BLOCKING` priority to validate segregation. + +Warm-up :: +* Discard initial 30 seconds to allow JIT compilation and cache priming. +* Verify monitor-loop metrics stabilise before collecting results. + +Sampling :: +* Persist HDR histograms for latency and jitter with 2 decimal microsecond precision. +* Capture CPU affinity maps and pauser states alongside results to prove configuration fidelity. + +Repeatability :: +* Run each scenario three times; publish mean and worst-case metrics. +* Store benchmark artefacts in build pipelines so regressions can be bisected. + +== Instrumentation Guidelines + +* Enable loop-block monitor logging at WARN to capture threshold breaches (THR-NF-O-019). +* Attach `PauserMonitorFactory` exporters to push pause counts and durations into time-series storage. +* Tag benchmark runs with Git commit, JVM build, and operating system version for traceability. + +== Regression Gates + +* CI pipelines must reject changes that exceed any target by more than 5 percent unless accompanied by an approved waiver referencing the relevant requirement ID. +* Nightly builds execute an extended soak (minimum 8 hours) to surface low-frequency jitter outliers; findings feed into operational run-books. + +== Reporting + +* Summarise performance results in release notes with explicit references to the requirements satisfied (e.g., "Maintains THR-NF-P-027 latency target"). +* Archive raw benchmark logs and histograms for audit and future tuning. +* When targets cannot be met on non-reference hardware, document compensating controls and adjustments to operational thresholds. diff --git a/src/main/docs/thread-security-review.adoc b/src/main/docs/thread-security-review.adoc new file mode 100644 index 000000000..d24a53a40 --- /dev/null +++ b/src/main/docs/thread-security-review.adoc @@ -0,0 +1,98 @@ += Chronicle Threads Security Review +:toc: +:sectnums: +:lang: en-GB + +== Handler Admission and Privilege Escalation + +Why :: +Handlers execute with the full privileges of the hosting JVM; untrusted code can compromise sensitive data paths. + +Core risks :: +* Malicious handler registration at runtime via exposed management endpoints (THR-FN-004). +* Unsandboxed handlers accessing shared mutable state or credentials. +* Reflection-based injection of handlers that bypass intended builder configuration (THR-FN-001). + +Mitigations :: +* Restrict handler installation to trusted bootstrap code paths; gate dynamic registration behind authentication and authorisation. +* Use code reviews and static analysis to enforce least-privilege principles within handlers. +* Log handler class names and source artefacts during registration for audit trails. + +Review hot-spots :: +* Deployment scripts that allow arbitrary classpath extensions. +* OSGi or plugin frameworks injecting handlers dynamically. + +== Affinity and Resource Isolation + +Why :: +Incorrect core binding can leak workload information across tenants or undermine performance isolation. + +Core risks :: +* Shared core usage permits timing side channels between sensitive workloads. +* NUMA misalignment causes cross-node memory access patterns exposing high-resolution timing data (THR-FN-017). +* System properties overridden by untrusted inputs altering affinity strings (THR-OPS-023). + +Mitigations :: +* Validate affinity strings against an approved list before instantiating `EventGroup` builders (THR-FN-015). +* Store affinity selections in configuration repositories with change control. +* Monitor actual thread-to-core bindings via OS tooling (e.g., `taskset`, `ps -Lo pid,psr`) and alert on drift. + +Review hot-spots :: +* Container orchestrators with relaxed CPU quotas. +* Multi-tenant hosts lacking hardware partitioning. + +== Monitoring and Telemetry Integrity + +Why :: +Accurate telemetry is essential for detecting anomalous behaviour and limit breaches. + +Core risks :: +* Attackers disable loop-block monitoring through system properties (THR-OPS-020). +* Log tampering obscures stack traces that evidence suspicious handler execution times (THR-NF-O-019). +* Telemetry collectors overloaded by attacker-generated events, leading to blind spots (THR-NF-O-021). + +Mitigations :: +* Lock down JVM arguments in production; apply checksum or signature validation to launch scripts. +* Forward critical monitor events to secure log aggregation platforms with tamper detection. +* Rate-limit telemetry ingestion and validate payload sizes from handlers publishing metrics. + +Review hot-spots :: +* Support run-books that recommend disabling monitors during troubleshooting. +* Nodes operating with reduced logging due to storage constraints. + +== Shutdown and Resource Hygiene + +Why :: +Handlers often manage off-heap or file-backed resources via Chronicle Core abstractions; improper shutdown can leak descriptors or expose data. + +Core risks :: +* `EventGroup` instances left open, keeping sensitive files mapped (THR-FN-002). +* Shutdown hooks overridden by untrusted code, preventing orderly release (THR-OPS-025). +* Race conditions during shutdown causing inconsistent state for dependent services. + +Mitigations :: +* Apply Chronicle Core's `ReferenceCounted` policies, ensuring handlers close dependent resources during loop shutdown. +* Harden shutdown hook registration; disallow multiple components from mutating the same hook. +* Capture and audit shutdown logs for every production cycle. + +Review hot-spots :: +* Handlers that interact with Chronicle Queue or Chronicle Map without corresponding close semantics. +* Scripted restarts that do not wait for `EventGroup.close()` completion. + +== Supply Chain and Dependency Considerations + +Why :: +Chronicle Threads relies on Chronicle Core and Affinity; vulnerabilities propagate through these dependencies. + +Core risks :: +* Outdated dependencies lacking recent security patches or mitigations. +* Misaligned versions introducing behavioural regressions in pauser or affinity handling. + +Mitigations :: +* Track dependency versions in BOM files; enforce minimum patch levels aligned with security advisories. +* Execute dependency-update dry runs in staging to validate core functionality and performance targets. +* Subscribe to Chronicle Software security bulletins and integrate alerts into incident response procedures. + +Review hot-spots :: +* Custom forks of Chronicle libraries. +* Environments that block outbound network access, delaying vulnerability scanning updates. diff --git a/src/main/docs/thread-thread-safety-guide.adoc b/src/main/docs/thread-thread-safety-guide.adoc new file mode 100644 index 000000000..4aae0f226 --- /dev/null +++ b/src/main/docs/thread-thread-safety-guide.adoc @@ -0,0 +1,56 @@ += Chronicle Threads Thread-Safety Guide +:toc: +:sectnums: +:lang: en-GB + +== Scope + +This guide explains how Chronicle Threads enforces single-threaded handler execution and how developers should structure code that interacts with event loops. +It expands on requirements THR-FN-006 through THR-NF-O-009 and aligns with Chronicle Core's `SingleThreadedChecked` utilities. + +== Event Loop Ownership Model + +* Each `EventLoop` runs on a dedicated Java platform thread; handlers registered on that loop must not share mutable state with other threads without explicit synchronisation (THR-FN-006). +* When a handler needs to hand over work to another thread (e.g., a blocking worker), use thread-safe queues or Chronicle Queue to transfer data without violating loop confinement. +* The boolean result of `action()` should reflect whether more immediate work is available; returning `true` repeatedly for idle handlers forces tight scheduling and increases contention for other handlers (THR-FN-007). + +== Safe Hand-off Patterns + +Initialise :: Construct handlers and supporting resources on the main thread, then call `singleThreadedCheckReset()` before registering them with the target loop. + +Operate :: Once registered, treat handler state as confined to the loop's thread. +All mutations should occur inside `action()` or helper methods invoked from that loop. + +Dispose :: Use `InvalidEventHandlerException.reusable()` to self-deregister when the handler has completed its lifecycle (THR-FN-008). +Ensure downstream resources honour Chronicle Core's `Closeable` and `ReferenceCounted` contracts. + +== Interaction with Shared Services + +* Shared caches or maps must expose lock-free APIs that are safe for single-writer, multi-reader scenarios, or provide appropriate synchronisation. +* When invoking Chronicle Queue appenders or tailers from handlers, rely on their single-threaded guarantees and avoid sharing instances across loops without resetting ownership. +* If a handler must update shared analytics or metrics collectors, prefer non-blocking data structures (e.g., `LongAdder`) to minimise stall risk. + +== Error Handling Discipline + +Unchecked exceptions :: +* The loop removes the offending handler and logs via `Jvm.warn()`; implement catch-and-report patterns where recovery is possible (THR-NF-O-009). + +Timeouts :: +* Use monitor-loop thresholds to detect blocked handlers early (THR-NF-O-018). +Handlers can emit domain-specific heartbeats to aid diagnosis. + +Defensive coding :: +* Validate external inputs before entering tight loops to avoid unbounded CPU usage. +* Leverage Chronicle Core's `SingleThreadedChecked` exceptions during testing to catch accidental cross-thread access. + +== Testing Strategies + +* Run unit tests with assertions enabled to surface `SingleThreadedChecked` violations. +* Use deterministic executors in integration tests to simulate loop progression and ensure handlers remain idempotent. +* Incorporate concurrency stress tests that replay boundary scenarios (e.g., handler self-deregistration while monitor loop samples metrics). + +== Documentation and Traceability + +* Annotate handler classes with the relevant requirement IDs (e.g., `THR-FN-006`) in code comments or design docs to aid reviews. +* Update operational run-books to describe ownership expectations and hand-off procedures. +* Ensure new handlers ship with accompanying tests that prove thread-safety assumptions, referencing requirement IDs in test names where practical. diff --git a/src/main/java/net/openhft/chronicle/threads/AbstractLifecycleEventLoop.java b/src/main/java/net/openhft/chronicle/threads/AbstractLifecycleEventLoop.java index 43e458a37..d51ce868c 100644 --- a/src/main/java/net/openhft/chronicle/threads/AbstractLifecycleEventLoop.java +++ b/src/main/java/net/openhft/chronicle/threads/AbstractLifecycleEventLoop.java @@ -54,7 +54,7 @@ public abstract class AbstractLifecycleEventLoop extends AbstractCloseable imple private static final long AWAIT_TERMINATION_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(5); private final AtomicReference lifecycle = new AtomicReference<>(EventLoopLifecycle.NEW); protected final String name; - boolean privateGroup; + private volatile boolean privateGroup; /** * Create an instance with the supplied name. @@ -72,7 +72,7 @@ protected AbstractLifecycleEventLoop(@NotNull String name) { singleThreadedCheckDisabled(true); } - protected String nameWithSlash() { + protected final String nameWithSlash() { return withSlash(name); } @@ -177,4 +177,8 @@ static String withSlash(String n) { public void privateGroup(boolean privateGroup) { this.privateGroup = privateGroup; } + + protected final boolean isPrivateGroup() { + return privateGroup; + } } diff --git a/src/main/java/net/openhft/chronicle/threads/BlockingEventLoop.java b/src/main/java/net/openhft/chronicle/threads/BlockingEventLoop.java index 4629700df..11a9ce971 100644 --- a/src/main/java/net/openhft/chronicle/threads/BlockingEventLoop.java +++ b/src/main/java/net/openhft/chronicle/threads/BlockingEventLoop.java @@ -47,7 +47,7 @@ * {@link net.openhft.chronicle.core.threads.HandlerPriority#BLOCKING} * are accepted but treated the same as blocking handlers.

*/ -public class BlockingEventLoop extends AbstractLifecycleEventLoop implements EventLoop { +public class BlockingEventLoop extends AbstractLifecycleEventLoop { @NotNull private transient final EventLoop parent; @@ -106,7 +106,7 @@ private void startHandler(final EventHandler handler) { try { final Runner runner = new Runner(handler, pauserSupplier.get()); runners.add(runner); - service.submit(runner); + service.execute(runner); } catch (RejectedExecutionException e) { if (!service.isShutdown()) @@ -161,7 +161,7 @@ public String toString() { @Override public boolean isRunningOnThread(Thread thread) { - for (int i=0; i < runners.size(); i++) { + for (int i = 0; i < runners.size(); i++) { if (thread == runners.get(i).thread()) { return true; } @@ -175,7 +175,7 @@ private final class Runner implements Runnable { private boolean endedGracefully = false; private transient volatile Thread thread = null; - public Runner(final EventHandler handler, Pauser pauser) { + Runner(final EventHandler handler, Pauser pauser) { this.handler = handler; this.pauser = pauser; } @@ -195,7 +195,9 @@ public void run() { } endedGracefully = true; } catch (InvalidEventHandlerException e) { - // expected and logged below. + if (Jvm.isDebugEnabled(handler.getClass())) { + Jvm.debug().on(handler.getClass(), "Handler removed after InvalidEventHandlerException"); + } } catch (Throwable t) { if (!isClosed()) Jvm.warn().on(handler.getClass(), asString(handler) + " threw ", t); diff --git a/src/main/java/net/openhft/chronicle/threads/DiskSpaceMonitor.java b/src/main/java/net/openhft/chronicle/threads/DiskSpaceMonitor.java index 0fc184368..cb9434297 100644 --- a/src/main/java/net/openhft/chronicle/threads/DiskSpaceMonitor.java +++ b/src/main/java/net/openhft/chronicle/threads/DiskSpaceMonitor.java @@ -67,7 +67,7 @@ public enum DiskSpaceMonitor implements Runnable, Closeable { final Map fileStoreCacheMap = new ConcurrentHashMap<>(); final Map diskAttributesMap = new ConcurrentHashMap<>(); final ScheduledExecutorService executor; - private int thresholdPercentage = Jvm.getInteger("chronicle.disk.monitor.threshold.percent", 5); + private volatile int thresholdPercentage = Jvm.getInteger("chronicle.disk.monitor.threshold.percent", 5); private TimeProvider timeProvider = SystemTimeProvider.INSTANCE; DiskSpaceMonitor() { @@ -117,7 +117,7 @@ public void pollDiskSpace(File file) { return; } } - DiskAttributes da = diskAttributesMap.computeIfAbsent(fs, DiskAttributes::new); + diskAttributesMap.computeIfAbsent(fs, DiskAttributes::new); final long tookUs = (timeProvider.currentTimeNanos() - start) / 1_000; if (tookUs > TIME_TAKEN_WARN_THRESHOLD_US) @@ -142,6 +142,14 @@ public int getThresholdPercentage() { return thresholdPercentage; } + /** + * Retains a public setter so callers can adjust the low disk warning threshold. + * + *

The legacy API expects {@link #INSTANCE} to expose threshold tuning, so the method + * remains public instead of adopting package-private visibility.

+ * + * @param thresholdPercentage percentage of capacity that triggers a warning + */ public void setThresholdPercentage(int thresholdPercentage) { this.thresholdPercentage = thresholdPercentage; } @@ -183,7 +191,8 @@ void run() throws IOException { notifyDiskLow.panic(fileStore); } else if (unallocatedBytes < totalSpace * DiskSpaceMonitor.INSTANCE.thresholdPercentage / 100) { - final double diskSpaceFull = ((long) (1000d * (totalSpace - unallocatedBytes) / totalSpace + 0.999)) / 10.0; + final double usedFraction = (double) (totalSpace - unallocatedBytes) / (double) totalSpace; + final double diskSpaceFull = Math.round(usedFraction * 1000.0) / 10.0; notifyDiskLow.warning(diskSpaceFull, fileStore); } else { @@ -191,8 +200,10 @@ void run() throws IOException { timeNextCheckedMS = now + (unallocatedBytes >> 20); } long time = System.nanoTime() - start; - if (time > 1_000_000) - Jvm.perf().on(getClass(), "Took " + time / 10_000 / 100.0 + " ms to check the disk space of " + fileStore); + if (time > 1_000_000) { + double millis = time / 1_000_000.0; + Jvm.perf().on(getClass(), "Took " + millis + " ms to check the disk space of " + fileStore); + } } } diff --git a/src/main/java/net/openhft/chronicle/threads/EventGroup.java b/src/main/java/net/openhft/chronicle/threads/EventGroup.java index 444e0532a..153e9e7e3 100644 --- a/src/main/java/net/openhft/chronicle/threads/EventGroup.java +++ b/src/main/java/net/openhft/chronicle/threads/EventGroup.java @@ -66,9 +66,7 @@ * eg.start(); * */ -public class EventGroup - extends AbstractLifecycleEventLoop - implements EventLoop { +public class EventGroup extends AbstractLifecycleEventLoop { public static final int CONC_THREADS = Jvm.getInteger("eventGroup.conc.threads", Jvm.getInteger("CONC_THREADS", Math.max(1, Runtime.getRuntime().availableProcessors() / 4))); @@ -97,7 +95,7 @@ public class EventGroup private VanillaEventLoop replication; @Deprecated(/* Instead use EventGroupBuilder. TODO: make package-private and undeprecate in x.28, as only EventGroupBuilder should be using */) - @SuppressWarnings({"this-escape", "deprecation"}) + @SuppressWarnings({"PMD.NullAssignment", "this-escape", "deprecation"}) public EventGroup(final boolean daemon, @NotNull final Pauser pauser, final Pauser replicationPauser, @@ -256,12 +254,11 @@ public void addHandler(@NotNull final EventHandler handler) { getReplication().addHandler(handler); break; - case CONCURRENT: { + case CONCURRENT: if (concThreads.isEmpty()) throw new IllegalStateException("Cannot add CONCURRENT " + handler + " to " + name); getConcThread(counter.getAndIncrement() % concThreads.size()).addHandler(handler); break; - } default: throw new IllegalArgumentException("Unknown priority " + handler.priority()); @@ -406,8 +403,8 @@ public boolean runsInsideCoreLoop() { @Override public boolean isRunningOnThread(Thread thread) { return core != null && core.isRunningOnThread(thread) || - blocking != null && blocking.isRunningOnThread(thread) || - monitor.isRunningOnThread(thread); + blocking != null && blocking.isRunningOnThread(thread) || + monitor.isRunningOnThread(thread); } @Override diff --git a/src/main/java/net/openhft/chronicle/threads/EventGroupBuilder.java b/src/main/java/net/openhft/chronicle/threads/EventGroupBuilder.java index b658218f3..245250db1 100644 --- a/src/main/java/net/openhft/chronicle/threads/EventGroupBuilder.java +++ b/src/main/java/net/openhft/chronicle/threads/EventGroupBuilder.java @@ -48,7 +48,7 @@ * .build(); * */ -public class EventGroupBuilder implements Builder { +public final class EventGroupBuilder implements Builder { private boolean daemon = true; private Pauser pauser; @@ -263,7 +263,7 @@ public EventGroupBuilder withConcurrentPauserSupplier(@NotNull Supplier * @return this builder */ public EventGroupBuilder withPriorities(Set priorities) { - this.priorities = priorities; + this.priorities = EnumSet.copyOf(priorities); return this; } @@ -271,7 +271,7 @@ public EventGroupBuilder withPriorities(Set priorities) { * Convenience overload to build a priority set from the given arguments. * * @param firstPriority first priority in the set - * @param priorities remaining priorities + * @param priorities remaining priorities * @return this builder */ public EventGroupBuilder withPriorities(HandlerPriority firstPriority, HandlerPriority... priorities) { diff --git a/src/main/java/net/openhft/chronicle/threads/MediumEventLoop.java b/src/main/java/net/openhft/chronicle/threads/MediumEventLoop.java index 6fe41ea29..79fbe9727 100644 --- a/src/main/java/net/openhft/chronicle/threads/MediumEventLoop.java +++ b/src/main/java/net/openhft/chronicle/threads/MediumEventLoop.java @@ -44,7 +44,7 @@ * The main loop runs on one thread and repeatedly executes HIGH then MEDIUM * handlers before pausing via the supplied {@link Pauser}. */ -public class MediumEventLoop extends AbstractLifecycleEventLoop implements CoreEventLoop, Runnable, Closeable { +public class MediumEventLoop extends AbstractLifecycleEventLoop implements CoreEventLoop, Runnable { public static final Set ALLOWED_PRIORITIES = Collections.unmodifiableSet( EnumSet.of(HandlerPriority.HIGH, @@ -154,7 +154,7 @@ public String toString() { protected void performStart() { synchronized (startStopMutex) { try { - service.submit(this); + service.execute(this); } catch (RejectedExecutionException e) { if (!isStopped()) { closeAll(); @@ -315,9 +315,9 @@ protected void loopFinishedAllHandlers() { if (!mediumHandlers.isEmpty()) mediumHandlers.forEach(Threads::loopFinishedQuietly); newHandlers.forEach(eventHandler -> { - Jvm.startup().on(getClass(), "Handler in newHandler was not accepted before loop finished " + eventHandler); - loopFinishedQuietly(eventHandler); - }); + Jvm.startup().on(getClass(), "Handler in newHandler was not accepted before loop finished " + eventHandler); + loopFinishedQuietly(eventHandler); + }); } /** @@ -380,55 +380,16 @@ private void closeAll() { dumpRunningHandlers(); } - // Unrolled to avoid megamorphic call chains. - @SuppressWarnings("fallthrough") private boolean runAllMediumHandler() { boolean busy = false; final EventHandler[] handlers = this.mediumHandlersArray; try { - switch (handlers.length) { - default: - for (int i = handlers.length - 1; i >= 4; i--) { - try { - busy |= handlers[i].action(); - } catch (Exception e) { - handleExceptionMediumHandler(handlers[i], e); - } - } - // fallthrough. - - case 4: - try { - busy |= handlers[3].action(); - } catch (Exception e) { - handleExceptionMediumHandler(handlers[3], e); - } - // fall through - case 3: - try { - busy |= handlers[2].action(); - } catch (Exception e) { - handleExceptionMediumHandler(handlers[2], e); - } - // fall through - case 2: - try { - busy |= handlers[1].action(); - } catch (Exception e) { - handleExceptionMediumHandler(handlers[1], e); - } - // fall through - case 1: { - try { - busy |= handlers[0].action(); - } catch (Exception e) { - handleExceptionMediumHandler(handlers[0], e); - } - break; + for (int i = handlers.length - 1; i >= 0; i--) { + try { + busy |= handlers[i].action(); + } catch (Exception e) { + handleExceptionMediumHandler(handlers[i], e); } - case 0: - break; - } } catch (Throwable e) { Jvm.warn().on(getClass(), e); @@ -436,66 +397,19 @@ private boolean runAllMediumHandler() { return busy; } - // Unrolled to reduce megamorphic calls and keep the JIT hot. - @SuppressWarnings("fallthrough") protected boolean runAllHandlers() { boolean busy = false; final EventHandler[] handlers = this.mediumHandlersArray; try { - // run HIGH handler busy |= callHighHandler(); - - switch (handlers.length) { - default: - for (int i = handlers.length - 1; i >= 4; i--) { - busy |= callHighHandler(); - try { - busy |= handlers[i].action(); - } catch (Exception e) { - handleExceptionMediumHandler(handlers[i], e); - } - } - // fallthrough. - - case 4: - busy |= callHighHandler(); - try { - busy |= handlers[3].action(); - } catch (Exception e) { - handleExceptionMediumHandler(handlers[3], e); - } - // fall through - case 3: - busy |= callHighHandler(); - try { - busy |= handlers[2].action(); - } catch (Exception e) { - handleExceptionMediumHandler(handlers[2], e); - } - // fall through - case 2: - busy |= callHighHandler(); - try { - busy |= handlers[1].action(); - } catch (Exception e) { - handleExceptionMediumHandler(handlers[1], e); - } - // fall through - case 1: { - busy |= callHighHandler(); - try { - busy |= handlers[0].action(); - } catch (Exception e) { - handleExceptionMediumHandler(handlers[0], e); - } - break; + for (int i = handlers.length - 1; i >= 0; i--) { + try { + busy |= handlers[i].action(); + } catch (Exception e) { + handleExceptionMediumHandler(handlers[i], e); } - case 0: - break; - + busy |= callHighHandler(); } - - // run HIGH handler again busy |= callHighHandler(); } catch (Throwable e) { Jvm.warn().on(getClass(), e); @@ -514,9 +428,9 @@ private boolean callHighHandler() { return true; } - protected void removeHighHandler() { - if (DEBUG_REMOVING_HANDLERS) - Jvm.debug().on(getClass(), "Removing " + highHandler.priority() + " " + highHandler + " from " + this.name); + protected void removeHighHandler() { + if (DEBUG_REMOVING_HANDLERS) + Jvm.debug().on(getClass(), "Removing " + highHandler.priority() + " " + highHandler + " from " + this.name); Threads.loopFinishedQuietly(highHandler); Closeable.closeQuietly(highHandler); highHandler = EventHandlers.NOOP; @@ -557,41 +471,31 @@ private boolean acceptNewHandlers() { return result; } - @SuppressWarnings("fallthrough") protected void addNewHandler(@NotNull final EventHandler handler) { - final HandlerPriority t1 = handler.priority(); - switch (t1.alias()) { - case HIGH: - if (updateHighHandler(handler)) { - break; - } else { - Jvm.warn().on(getClass(), "Only one high handler supported was " + highHandler + ", treating " + handler + " as MEDIUM"); - // fall through to MEDIUM - } + HandlerPriority alias = handler.priority().alias(); + if (alias == HandlerPriority.HIGH && !updateHighHandler(handler)) { + Jvm.warn().on(getClass(), "Only one high handler supported was " + highHandler + ", treating " + handler + " as MEDIUM"); + alias = HandlerPriority.MEDIUM; + } - case REPLICATION: - case CONCURRENT: - case DAEMON: - case MEDIUM: { - if (!mediumHandlers.contains(handler)) { - clearUsedByThread(handler); - handler.eventLoop(parent != null ? parent : this); - mediumHandlers.add(handler); - updateMediumHandlersArray(); - } - break; + if (alias == HandlerPriority.REPLICATION + || alias == HandlerPriority.CONCURRENT + || alias == HandlerPriority.DAEMON + || alias == HandlerPriority.MEDIUM) { + if (!mediumHandlers.contains(handler)) { + clearUsedByThread(handler); + handler.eventLoop(parent != null ? parent : this); + mediumHandlers.add(handler); + updateMediumHandlersArray(); } - - case MONITOR: - if (parent != null) { - Jvm.warn().on(getClass(), "Handler " + handler.getClass() + " ignored"); - return; - } - - case BLOCKING: - case TIMER: - default: - throw new IllegalArgumentException("Cannot add a " + handler.priority() + " task to a busy waiting thread"); + } else if (alias == HandlerPriority.MONITOR) { + if (parent != null) { + Jvm.warn().on(getClass(), "Handler " + handler.getClass() + " ignored"); + return; + } + throw new IllegalArgumentException("Cannot add a " + handler.priority() + " task to a busy waiting thread"); + } else if (alias != HandlerPriority.HIGH) { + throw new IllegalArgumentException("Cannot add a " + handler.priority() + " task to a busy waiting thread"); } if (thread == Thread.currentThread()) { @@ -652,9 +556,9 @@ protected void closeAllHandlers() { Closeable.closeQuietly(highHandler); closeAll(mediumHandlers); newHandlers.forEach(eventHandler -> { - Jvm.startup().on(getClass(), "Handler in newHandler was not accepted before close " + eventHandler); - Closeable.closeQuietly(eventHandler); - }); + Jvm.startup().on(getClass(), "Handler in newHandler was not accepted before close " + eventHandler); + Closeable.closeQuietly(eventHandler); + }); } public void dumpRunningHandlers() { @@ -691,21 +595,25 @@ protected void performClose() { } } + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") private void shutdownService() { - LockSupport.unpark(thread); - if (privateGroup) { + final Thread threadSnapshot = thread; + if (threadSnapshot != null) { + LockSupport.unpark(threadSnapshot); + } + if (isPrivateGroup()) { service.shutdownNow(); return; } Threads.shutdown(service, daemon); - if (thread != null && thread != Thread.currentThread()) { + if (threadSnapshot != null && threadSnapshot != Thread.currentThread()) { long startTimeMillis = System.currentTimeMillis(); long waitUntilMs = startTimeMillis; - thread.interrupt(); + threadSnapshot.interrupt(); for (int i = 1; i <= 50; i++) { - if (!thread.isAlive()) + if (!threadSnapshot.isAlive()) break; // we do this loop below to protect from Jvm.pause not pausing for as long as it should waitUntilMs += i; @@ -716,9 +624,9 @@ private void shutdownService() { final StringBuilder sb = new StringBuilder(); long ms = System.currentTimeMillis() - startTimeMillis; sb.append(name).append(": Shutting down thread is executing after "). - append(ms).append("ms ").append(thread) + append(ms).append("ms ").append(threadSnapshot) .append(", " + "handlerCount=").append(nonDaemonHandlerCount()); - Jvm.trimStackTrace(sb, thread.getStackTrace()); + Jvm.trimStackTrace(sb, threadSnapshot.getStackTrace()); Jvm.warn().on(getClass(), sb.toString()); dumpRunningHandlers(); } diff --git a/src/main/java/net/openhft/chronicle/threads/MonitorEventLoop.java b/src/main/java/net/openhft/chronicle/threads/MonitorEventLoop.java index baa7372c0..eb5604c62 100644 --- a/src/main/java/net/openhft/chronicle/threads/MonitorEventLoop.java +++ b/src/main/java/net/openhft/chronicle/threads/MonitorEventLoop.java @@ -41,7 +41,7 @@ *

The loop waits for {@link #MONITOR_INITIAL_DELAY_MS} milliseconds after startup before * invoking any handlers.

*/ -public class MonitorEventLoop extends AbstractLifecycleEventLoop implements Runnable, EventLoop { +public class MonitorEventLoop extends AbstractLifecycleEventLoop implements Runnable { public static final String MONITOR_INITIAL_DELAY = "MonitorInitialDelay"; static int MONITOR_INITIAL_DELAY_MS = Jvm.getInteger(MONITOR_INITIAL_DELAY, 10_000); @@ -65,7 +65,7 @@ public MonitorEventLoop(final EventLoop parent, final String name, final Pauser @Override protected void performStart() { - service.submit(this); + service.execute(this); } @Override @@ -200,7 +200,7 @@ private static final class IdempotentLoopStartedEventHandler extends SimpleClose private final String handler; private boolean loopStarted = false; - public IdempotentLoopStartedEventHandler(@NotNull EventHandler eventHandler) { + IdempotentLoopStartedEventHandler(@NotNull EventHandler eventHandler) { this.eventHandler = eventHandler; handler = eventHandler.toString(); } diff --git a/src/main/java/net/openhft/chronicle/threads/NamedThreadFactory.java b/src/main/java/net/openhft/chronicle/threads/NamedThreadFactory.java index c5963e057..d42fef4b2 100644 --- a/src/main/java/net/openhft/chronicle/threads/NamedThreadFactory.java +++ b/src/main/java/net/openhft/chronicle/threads/NamedThreadFactory.java @@ -58,6 +58,7 @@ public NamedThreadFactory(String name, Boolean daemon, Integer priority) { * @param priority priority to assign or {@code null} for the JVM default * @param inEventLoop mark threads as part of an event loop for monitoring */ + @SuppressWarnings("PMD.NullAssignment") public NamedThreadFactory(String name, Boolean daemon, Integer priority, boolean inEventLoop) { super(name); this.nameShadow = name; diff --git a/src/main/java/net/openhft/chronicle/threads/PauserMonitorFactory.java b/src/main/java/net/openhft/chronicle/threads/PauserMonitorFactory.java index 7b620ce5d..6e341db8c 100644 --- a/src/main/java/net/openhft/chronicle/threads/PauserMonitorFactory.java +++ b/src/main/java/net/openhft/chronicle/threads/PauserMonitorFactory.java @@ -19,9 +19,9 @@ public interface PauserMonitorFactory { * Typical implementations will log the pause count or total time paused and * may alert if the pauser has remained idle for longer than {@code seconds}. * - * @param pauser the {@link Pauser} to monitor - * @param description label used in the monitor's {@code toString} - * @param seconds threshold before reporting prolonged pauses + * @param pauser the {@link Pauser} to monitor + * @param description label used in the monitor's {@code toString} + * @param seconds threshold before reporting prolonged pauses * @return an event handler suitable for a monitoring loop */ EventHandler pauserMonitor(Pauser pauser, String description, int seconds); @@ -35,6 +35,7 @@ static PauserMonitorFactory load() { public boolean action() throws InvalidEventHandlerException { throw new InvalidEventHandlerException(); } + @Override public String toString() { return "NOOP_PAUSER_MONITOR"; diff --git a/src/main/java/net/openhft/chronicle/threads/ThreadMonitors.java b/src/main/java/net/openhft/chronicle/threads/ThreadMonitors.java index de747810e..3c135fc2d 100644 --- a/src/main/java/net/openhft/chronicle/threads/ThreadMonitors.java +++ b/src/main/java/net/openhft/chronicle/threads/ThreadMonitors.java @@ -32,9 +32,9 @@ public enum ThreadMonitors { /** * Create a monitor for a single thread. * - * @param description text used in log messages - * @param timeLimit threshold in nanoseconds before a stack trace is logged - * @param timeSupplier supplies the current time, usually {@link System#nanoTime} + * @param description text used in log messages + * @param timeLimit threshold in nanoseconds before a stack trace is logged + * @param timeSupplier supplies the current time, usually {@link System#nanoTime} * @param threadSupplier returns the thread to observe * @return a monitor handler for installation on a monitor loop */ @@ -74,9 +74,9 @@ public static ThreadMonitor forThread(String description, long timeLimit, /** * Create a monitor aimed at a service thread. * - * @param description text used in log messages - * @param timeLimit threshold in nanoseconds before a stack trace is logged - * @param timeSupplier supplies the current time + * @param description text used in log messages + * @param timeLimit threshold in nanoseconds before a stack trace is logged + * @param timeSupplier supplies the current time * @param threadSupplier returns the thread to observe * @return a monitor handler for installation on a monitor loop */ diff --git a/src/main/java/net/openhft/chronicle/threads/Threads.java b/src/main/java/net/openhft/chronicle/threads/Threads.java index 50f941784..59508bf96 100644 --- a/src/main/java/net/openhft/chronicle/threads/Threads.java +++ b/src/main/java/net/openhft/chronicle/threads/Threads.java @@ -249,7 +249,7 @@ static void forEachThread(ExecutorService service, Consumer consumer) { for (Object o : objects) { Thread t = Jvm.getValue(o, "thread"); - if (t.getState() != State.TERMINATED) + if (t != null && t.getState() != State.TERMINATED) consumer.accept(t); } } catch (Exception e) { @@ -283,7 +283,7 @@ private static ExecutorService resolveDelegatedExecutorServices(@NotNull Executo } } } catch (IllegalAccessException | IllegalArgumentException error) { - // We can't access the field, move on + Jvm.debug().on(Threads.class, "Unable to resolve delegated executor: " + error); } return executorService; } diff --git a/src/main/java/net/openhft/chronicle/threads/TimedEventHandler.java b/src/main/java/net/openhft/chronicle/threads/TimedEventHandler.java index 0a4a515ab..c77846bc7 100644 --- a/src/main/java/net/openhft/chronicle/threads/TimedEventHandler.java +++ b/src/main/java/net/openhft/chronicle/threads/TimedEventHandler.java @@ -41,7 +41,9 @@ * */ public abstract class TimedEventHandler implements EventHandler { - /** next scheduled run time in {@link System#nanoTime()} units. */ + /** + * next scheduled run time in {@link System#nanoTime()} units. + */ private long nextRunNS = 0; /** diff --git a/src/main/java/net/openhft/chronicle/threads/VanillaEventLoop.java b/src/main/java/net/openhft/chronicle/threads/VanillaEventLoop.java index bd3616745..ebbe3f5cb 100644 --- a/src/main/java/net/openhft/chronicle/threads/VanillaEventLoop.java +++ b/src/main/java/net/openhft/chronicle/threads/VanillaEventLoop.java @@ -77,11 +77,6 @@ public VanillaEventLoop(@Nullable final EventLoop parent, this.priorities = EnumSet.copyOf(priorities); } - public static void closeAll(@NotNull final List handlers) { - // do not remove the handler here, remove all at end instead - Closeable.closeQuietly(handlers); - } - private static void clearUsedByThread(@NotNull EventHandler handler) { if (handler instanceof AbstractCloseable) ((AbstractCloseable) handler).singleThreadedCheckReset(); @@ -165,47 +160,41 @@ private void runAllHandlers(List handlers) { * Routes new handlers to the appropriate queue. TIMER handlers are placed * in {@code timerHandlers} and DAEMON handlers go to {@code daemonHandlers}. */ - @SuppressWarnings("fallthrough") @Override protected void addNewHandler(@NotNull final EventHandler handler) { - final HandlerPriority t1 = handler.priority(); - switch (t1.alias()) { - case HIGH: - if (updateHighHandler(handler)) { - break; - } else { - Jvm.warn().on(getClass(), "Only one high handler supported was " + highHandler + ", treating " + handler + " as MEDIUM"); - // fall through to MEDIUM - } - - case MEDIUM: - if (!mediumHandlers.contains(handler)) { - clearUsedByThread(handler); - eventLoopQuietly(parent != null ? parent : this, handler); - mediumHandlers.add(handler); - mediumHandlers.sort(Comparator.comparing(EventHandler::priority).reversed()); - updateMediumHandlersArray(); - } - break; - - case TIMER: - if (!timerHandlers.contains(handler)) { - clearUsedByThread(handler); - eventLoopQuietly(parent != null ? parent : this, handler); - timerHandlers.add(handler); - } - break; - - case DAEMON: - if (!daemonHandlers.contains(handler)) { - clearUsedByThread(handler); - eventLoopQuietly(parent != null ? parent : this, handler); - daemonHandlers.add(handler); - } - break; + HandlerPriority alias = handler.priority().alias(); + boolean highHandled = false; + if (alias == HandlerPriority.HIGH) { + if (updateHighHandler(handler)) { + highHandled = true; + } else { + Jvm.warn().on(getClass(), "Only one high handler supported was " + highHandler + ", treating " + handler + " as MEDIUM"); + alias = HandlerPriority.MEDIUM; + } + } - default: - throw new IllegalArgumentException("Cannot add a " + handler.priority() + " task to a busy waiting thread"); + if (!highHandled && alias == HandlerPriority.MEDIUM) { + if (!mediumHandlers.contains(handler)) { + clearUsedByThread(handler); + eventLoopQuietly(parent != null ? parent : this, handler); + mediumHandlers.add(handler); + mediumHandlers.sort(Comparator.comparing(EventHandler::priority).reversed()); + updateMediumHandlersArray(); + } + } else if (!highHandled && alias == HandlerPriority.TIMER) { + if (!timerHandlers.contains(handler)) { + clearUsedByThread(handler); + eventLoopQuietly(parent != null ? parent : this, handler); + timerHandlers.add(handler); + } + } else if (!highHandled && alias == HandlerPriority.DAEMON) { + if (!daemonHandlers.contains(handler)) { + clearUsedByThread(handler); + eventLoopQuietly(parent != null ? parent : this, handler); + daemonHandlers.add(handler); + } + } else if (!highHandled) { + throw new IllegalArgumentException("Cannot add a " + handler.priority() + " task to a busy waiting thread"); } if (thread == Thread.currentThread()) { @@ -241,8 +230,8 @@ protected void performClose() { @Override protected void closeAllHandlers() { - closeAll(daemonHandlers); - closeAll(timerHandlers); + MediumEventLoop.closeAll(daemonHandlers); + MediumEventLoop.closeAll(timerHandlers); super.closeAllHandlers(); } diff --git a/src/main/java/net/openhft/chronicle/threads/VanillaExecutorFactory.java b/src/main/java/net/openhft/chronicle/threads/VanillaExecutorFactory.java index 2967f3b36..744a2b329 100644 --- a/src/main/java/net/openhft/chronicle/threads/VanillaExecutorFactory.java +++ b/src/main/java/net/openhft/chronicle/threads/VanillaExecutorFactory.java @@ -30,7 +30,9 @@ * always single-threaded.

*/ public enum VanillaExecutorFactory implements ExecutorFactory { - /** sole instance used by default */ + /** + * sole instance used by default + */ INSTANCE; @Override diff --git a/src/main/java/net/openhft/chronicle/threads/internal/EventLoopThreadHolder.java b/src/main/java/net/openhft/chronicle/threads/internal/EventLoopThreadHolder.java index c6d0f1978..9d623942f 100644 --- a/src/main/java/net/openhft/chronicle/threads/internal/EventLoopThreadHolder.java +++ b/src/main/java/net/openhft/chronicle/threads/internal/EventLoopThreadHolder.java @@ -19,6 +19,7 @@ import net.openhft.chronicle.core.Jvm; import net.openhft.chronicle.threads.CoreEventLoop; import net.openhft.chronicle.threads.ThreadHolder; + /** * {@link ThreadHolder} implementation used to monitor a single event loop * thread. It keeps track of how long the loop has been running and requests a @@ -70,7 +71,7 @@ public boolean shouldLog(long nowNS) { @Override public void dumpThread(long startedNS, long nowNS) { long blockingTimeNS = nowNS - startedNS; - double blockingTimeMS = blockingTimeNS / 100_000 / 10.0; + double blockingTimeMS = blockingTimeNS / 1_000_000.0; if (blockingTimeMS <= 0.0) return; eventLoop.dumpRunningState(eventLoop.name() + " thread has blocked for " diff --git a/src/main/java/net/openhft/chronicle/threads/internal/EventLoopUtil.java b/src/main/java/net/openhft/chronicle/threads/internal/EventLoopUtil.java index cac8b78f2..16bb83f2c 100644 --- a/src/main/java/net/openhft/chronicle/threads/internal/EventLoopUtil.java +++ b/src/main/java/net/openhft/chronicle/threads/internal/EventLoopUtil.java @@ -30,13 +30,19 @@ public enum EventLoopUtil { ; // none - /** Fallback when {@code eventloop.accept.mod} is not set. */ + /** + * Fallback when {@code eventloop.accept.mod} is not set. + */ private static final int DEFAULT_ACCEPT_HANDLER_MOD_COUNT = 128; - /** Interval for re-adding accept handlers. */ + /** + * Interval for re-adding accept handlers. + */ public static final int ACCEPT_HANDLER_MOD_COUNT = Jvm.getInteger("eventloop.accept.mod", DEFAULT_ACCEPT_HANDLER_MOD_COUNT); - /** True when accept handler re-arming is active. */ + /** + * True when accept handler re-arming is active. + */ public static final boolean IS_ACCEPT_HANDLER_MOD_COUNT = ACCEPT_HANDLER_MOD_COUNT > 0; } diff --git a/src/main/java/net/openhft/chronicle/threads/internal/ThreadsThreadHolder.java b/src/main/java/net/openhft/chronicle/threads/internal/ThreadsThreadHolder.java index 9e6fc4949..733bebb0d 100644 --- a/src/main/java/net/openhft/chronicle/threads/internal/ThreadsThreadHolder.java +++ b/src/main/java/net/openhft/chronicle/threads/internal/ThreadsThreadHolder.java @@ -53,12 +53,12 @@ public class ThreadsThreadHolder implements ThreadHolder { /** * Create an instance configured to monitor the supplied thread. * - * @param description text appended to log messages - * @param timeLimitNS threshold in nanoseconds before logging occurs - * @param timeSupplier provides the current time + * @param description text appended to log messages + * @param timeLimitNS threshold in nanoseconds before logging occurs + * @param timeSupplier provides the current time * @param threadSupplier supplies the thread to observe - * @param logEnabled predicate controlling whether logging happens - * @param logConsumer receives the formatted log message + * @param logEnabled predicate controlling whether logging happens + * @param logConsumer receives the formatted log message */ public ThreadsThreadHolder(String description, long timeLimitNS, LongSupplier timeSupplier, Supplier threadSupplier, BooleanSupplier logEnabled, Consumer logConsumer) { this.description = description; @@ -91,7 +91,8 @@ public long startedNS() { @Override public void monitorThreadDelayed(long actionCallDelayNS) { - logConsumer.accept("Monitor thread for " + getName() + " cpuId: " + Affinity.getCpu() + " was delayed by " + actionCallDelayNS / 100000 / 10.0 + " ms"); + double delayedMs = Math.round((actionCallDelayNS / 1_000_000.0) * 10.0) / 10.0; + logConsumer.accept("Monitor thread for " + getName() + " cpuId: " + Affinity.getCpu() + " was delayed by " + delayedMs + " ms"); } @Override @@ -124,10 +125,9 @@ public void dumpThread(long startedNS, long nowNS) { * @param timeInNS The time in nanoseconds * @return The time in milliseconds represented as a float with limited precision */ - @SuppressWarnings(/* we mean to do the integer division first */ - {"java:S2184", "IntegerDivisionInFloatingPointContext"}) static double nanosecondsToMillisWithTenthsPrecision(long timeInNS) { - return (timeInNS / 100_000) / 10d; + double millis = timeInNS / 1_000_000.0; + return Math.round(millis * 10.0) / 10.0; } @Override diff --git a/src/main/java/net/openhft/chronicle/threads/internal/package-info.java b/src/main/java/net/openhft/chronicle/threads/internal/package-info.java index 869d23d44..b1904745e 100644 --- a/src/main/java/net/openhft/chronicle/threads/internal/package-info.java +++ b/src/main/java/net/openhft/chronicle/threads/internal/package-info.java @@ -18,8 +18,8 @@ * This package and any and all sub-packages contains strictly internal classes for this Chronicle library. * Internal classes shall never be used directly. *

- * Specifically, the following actions (including, but not limited to) are not allowed - * on internal classes and packages: + * Specifically, the following actions (including, but not limited to) are not allowed + * on internal classes and packages: *

    *
  • Casting to
  • *
  • Reflection of any kind
  • diff --git a/src/test/java/net/openhft/chronicle/threads/DiskSpaceMonitorTest.java b/src/test/java/net/openhft/chronicle/threads/DiskSpaceMonitorTest.java index fa0211a1c..0435d813e 100644 --- a/src/test/java/net/openhft/chronicle/threads/DiskSpaceMonitorTest.java +++ b/src/test/java/net/openhft/chronicle/threads/DiskSpaceMonitorTest.java @@ -19,26 +19,31 @@ import net.openhft.chronicle.core.Jvm; import net.openhft.chronicle.core.onoes.ExceptionKey; import net.openhft.chronicle.core.time.SetTimeProvider; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.AfterEach; import java.io.File; +import java.lang.reflect.Constructor; +import java.nio.file.FileStore; +import java.nio.file.Files; +import java.nio.file.Paths; import java.time.Duration; +import java.util.Arrays; import java.util.Map; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assumptions.assumeTrue; public class DiskSpaceMonitorTest extends ThreadsTestCommon { @BeforeEach - public void beforeEach(){ + public void beforeEach() { clearState(); } @AfterEach - public void afterEach(){ + public void afterEach() { clearState(); } @@ -92,4 +97,56 @@ public void ensureThatDiskSpaceMonitorRunsForMoreThanOneIteration() throws Inter Thread.sleep(1000); } + @Test + public void notifyIteratorForwardsToAllDelegates() throws Exception { + Constructor constructor = Class.forName("net.openhft.chronicle.threads.DiskSpaceMonitor$NotifyDiskLowIterator") + .getDeclaredConstructor(java.util.List.class); + constructor.setAccessible(true); + + RecordingNotify first = new RecordingNotify(); + RecordingNotify second = new RecordingNotify(); + NotifyDiskLow aggregator = (NotifyDiskLow) constructor.newInstance(Arrays.asList(first, second)); + + FileStore store = Files.getFileStore(Paths.get(".")); + aggregator.warning(72.5, store); + aggregator.panic(store); + + for (RecordingNotify entry : Arrays.asList(first, second)) { + assertTrue(entry.warningCalled); + assertEquals(72.5, entry.lastWarningPercent, 0.001); + assertSame(store, entry.lastStore); + assertTrue(entry.panicCalled); + } + } + + @Test + public void publicSetterRetainsConfiguredThreshold() { + int original = DiskSpaceMonitor.INSTANCE.getThresholdPercentage(); + try { + DiskSpaceMonitor.INSTANCE.setThresholdPercentage(17); + assertEquals(17, DiskSpaceMonitor.INSTANCE.getThresholdPercentage()); + } finally { + DiskSpaceMonitor.INSTANCE.setThresholdPercentage(original); + } + } + + private static final class RecordingNotify implements NotifyDiskLow { + boolean panicCalled; + boolean warningCalled; + double lastWarningPercent; + FileStore lastStore; + + @Override + public void panic(FileStore fileStore) { + panicCalled = true; + lastStore = fileStore; + } + + @Override + public void warning(double diskSpaceFullPercent, FileStore fileStore) { + warningCalled = true; + lastWarningPercent = diskSpaceFullPercent; + lastStore = fileStore; + } + } } diff --git a/src/test/java/net/openhft/chronicle/threads/EventGroupHandlerTest.java b/src/test/java/net/openhft/chronicle/threads/EventGroupHandlerTest.java index 6ff3dc3b3..85b8e6d81 100644 --- a/src/test/java/net/openhft/chronicle/threads/EventGroupHandlerTest.java +++ b/src/test/java/net/openhft/chronicle/threads/EventGroupHandlerTest.java @@ -15,9 +15,12 @@ */ package net.openhft.chronicle.threads; -import net.openhft.chronicle.core.threads.*; +import net.openhft.chronicle.core.threads.EventLoop; +import net.openhft.chronicle.core.threads.HandlerPriority; import net.openhft.chronicle.testframework.Waiters; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static net.openhft.chronicle.threads.TestEventHandlers.*; import static org.junit.jupiter.api.Assertions.*; @@ -89,7 +92,7 @@ void addGoodHandlerBeforeStart(CountingHandler handler) { @Test void testGoodHandlerAddedBeforeStart() { - for(HandlerPriority priority : HandlerPriority.values()) { + for (HandlerPriority priority : HandlerPriority.values()) { addGoodHandlerBeforeStart(new CountingHandler(priority)); } } @@ -104,7 +107,7 @@ void addGoodHandlerAfterStart(CountingHandler handler) { // Add the handler. eventGroup.addHandler(handler); - Waiters.waitForCondition("Wait handler loopStarted called:" + handler.priority,() -> (handler.loopStartedCalled() > 0), 5000); + Waiters.waitForCondition("Wait handler loopStarted called:" + handler.priority, () -> (handler.loopStartedCalled() > 0), 5000); // Check the handler. assertEquals(1, handler.loopStartedCalled()); @@ -131,7 +134,7 @@ void addGoodHandlerAfterStart(CountingHandler handler) { @Test void testGoodHandlerAddedAfterStart() { - for(HandlerPriority priority : HandlerPriority.values()) { + for (HandlerPriority priority : HandlerPriority.values()) { addGoodHandlerAfterStart(new CountingHandler(priority)); } } @@ -223,7 +226,7 @@ void addThrowingHandlerAfterEventLoopStarted(CountingHandler handler) { eventGroup.addHandler(handler); // Wait for the handler to be removed. - Waiters.waitForCondition("Wait handler loopStarted called:" + handler.priority,() -> (handler.closeCalled() > 0), 5000); + Waiters.waitForCondition("Wait handler loopStarted called:" + handler.priority, () -> (handler.closeCalled() > 0), 5000); // Event loop is running. assertTrue(eventGroup.isAlive()); @@ -280,7 +283,7 @@ void addThrowingEventLoopAfterEventLoopStarted(CountingHandler handler) { // Add the new handler. It should be picked up by the event loop and exception in eventLoop logged and ignored. eventGroup.addHandler(handler); - Waiters.waitForCondition("Wait handler loopStarted called:" + handler.priority,() -> (handler.loopStartedCalled() > 0), 5000); + Waiters.waitForCondition("Wait handler loopStarted called:" + handler.priority, () -> (handler.loopStartedCalled() > 0), 5000); // Check the handler. assertEquals(1, handler.loopStartedCalled()); diff --git a/src/test/java/net/openhft/chronicle/threads/EventGroupTest.java b/src/test/java/net/openhft/chronicle/threads/EventGroupTest.java index b29c75b12..018088f43 100644 --- a/src/test/java/net/openhft/chronicle/threads/EventGroupTest.java +++ b/src/test/java/net/openhft/chronicle/threads/EventGroupTest.java @@ -41,7 +41,7 @@ import java.util.stream.Stream; import static java.util.Collections.singleton; -import static net.openhft.chronicle.core.io.Closeable.*; +import static net.openhft.chronicle.core.io.Closeable.closeQuietly; import static org.junit.jupiter.api.Assertions.*; /** @@ -621,7 +621,7 @@ void throwIt() { abstract void throwIt() throws InvalidEventHandlerException; } - private static class PausingBlockingEventHandler implements EventHandler { + private static final class PausingBlockingEventHandler implements EventHandler { @Override public boolean action() { LockSupport.parkNanos(Long.MAX_VALUE); diff --git a/src/test/java/net/openhft/chronicle/threads/LongPauserBenchmark.java b/src/test/java/net/openhft/chronicle/threads/LongPauserBenchmark.java index 328552d5a..a5e09eb11 100644 --- a/src/test/java/net/openhft/chronicle/threads/LongPauserBenchmark.java +++ b/src/test/java/net/openhft/chronicle/threads/LongPauserBenchmark.java @@ -22,7 +22,7 @@ /** * Benchmark used to gauge the overhead of waking a {@link LongPauser}. - * + *

    * A helper thread loops calling {@link LongPauser#pause()} and then yields. * The main thread repeatedly invokes {@link LongPauser#unpause()} a fixed * number of times and measures the elapsed time. Dividing the total by the diff --git a/src/test/java/net/openhft/chronicle/threads/LoopIntrospectionTest.java b/src/test/java/net/openhft/chronicle/threads/LoopIntrospectionTest.java new file mode 100644 index 000000000..e4f10269b --- /dev/null +++ b/src/test/java/net/openhft/chronicle/threads/LoopIntrospectionTest.java @@ -0,0 +1,161 @@ +/* + * Copyright 2016-2025 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.threads; + +import net.openhft.chronicle.core.Jvm; +import net.openhft.chronicle.core.threads.EventHandler; +import net.openhft.chronicle.core.threads.HandlerPriority; +import net.openhft.chronicle.core.threads.InvalidEventHandlerException; +import net.openhft.chronicle.testframework.Waiters; +import net.openhft.chronicle.threads.TestEventHandlers.CountingHandler; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; + +import java.util.EnumSet; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.*; + +class LoopIntrospectionTest extends ThreadsTestCommon { + + @Test + void mediumEventLoopReportsRunningThread() throws InterruptedException { + AtomicReference loopThread = new AtomicReference<>(); + CountDownLatch firstInvocation = new CountDownLatch(1); + + try (MediumEventLoop loop = new MediumEventLoop(null, "introspection-medium", + Pauser.balanced(), true, null)) { + loop.start(); + Waiters.waitForCondition("Medium loop did not start", loop::isStarted, 5_000); + + loop.addHandler(new EventHandler() { + @Override + public @NotNull HandlerPriority priority() { + return HandlerPriority.MEDIUM; + } + + @Override + public boolean action() { + loopThread.compareAndSet(null, Thread.currentThread()); + firstInvocation.countDown(); + return false; + } + }); + + assertTrue(firstInvocation.await(5, TimeUnit.SECONDS), "Handler never ran on medium loop"); + Thread executing = loopThread.get(); + assertNotNull(executing, "Medium loop thread was not captured"); + + assertTrue(loop.isRunningOnThread(executing), "Loop failed to recognise its worker thread"); + assertFalse(loop.isRunningOnThread(new Thread()), "Loop incorrectly matched unrelated thread"); + } + } + + @Test + void blockingEventLoopReportsRunningThread() throws InterruptedException { + AtomicReference loopThread = new AtomicReference<>(); + CountDownLatch firstInvocation = new CountDownLatch(1); + + try (BlockingEventLoop loop = new BlockingEventLoop("introspection-blocking")) { + loop.start(); + + loop.addHandler(() -> { + loopThread.compareAndSet(null, Thread.currentThread()); + firstInvocation.countDown(); + Jvm.pause(10); + return false; + }); + + assertTrue(firstInvocation.await(5, TimeUnit.SECONDS), "Handler never ran on blocking loop"); + Thread executing = loopThread.get(); + assertNotNull(executing, "Blocking loop thread was not captured"); + + assertTrue(loop.isRunningOnThread(executing), "Blocking loop failed to recognise its worker thread"); + assertFalse(loop.isRunningOnThread(Thread.currentThread()), "Blocking loop matched caller thread"); + } + } + + @Test + void eventGroupAggregatesRunningThreadChecks() throws InterruptedException { + AtomicReference highThread = new AtomicReference<>(); + AtomicReference blockingThread = new AtomicReference<>(); + AtomicReference monitorThread = new AtomicReference<>(); + + int previousDelay = MonitorEventLoop.MONITOR_INITIAL_DELAY_MS; + MonitorEventLoop.MONITOR_INITIAL_DELAY_MS = 1; + try (EventGroup group = EventGroup.builder() + .withPriorities(EnumSet.of(HandlerPriority.HIGH, HandlerPriority.BLOCKING, HandlerPriority.MONITOR)) + .withPauser(Pauser.balanced()) + .build()) { + group.start(); + Waiters.waitForCondition("Event group did not start", group::isStarted, 5_000); + + group.addHandler(new RecordingHandler(HandlerPriority.HIGH, highThread)); + group.addHandler(new RecordingHandler(HandlerPriority.BLOCKING, blockingThread)); + group.addHandler(new RecordingHandler(HandlerPriority.MONITOR, monitorThread)); + + Waiters.waitForCondition("High loop thread not captured", () -> highThread.get() != null, 5_000); + Waiters.waitForCondition("Blocking loop thread not captured", () -> blockingThread.get() != null, 5_000); + Waiters.waitForCondition("Monitor loop thread not captured", () -> monitorThread.get() != null, 5_000); + + assertTrue(group.isRunningOnThread(highThread.get()), "Group did not recognise high-priority loop thread"); + assertTrue(group.isRunningOnThread(blockingThread.get()), "Group did not recognise blocking loop thread"); + assertTrue(group.isRunningOnThread(monitorThread.get()), "Group did not recognise monitor loop thread"); + assertFalse(group.isRunningOnThread(new Thread()), "Group matched unrelated thread"); + } finally { + MonitorEventLoop.MONITOR_INITIAL_DELAY_MS = previousDelay; + } + } + + @Test + void mediumEventLoopClosesPendingHandlersOnClose() { + CountingHandler handler = new CountingHandler(HandlerPriority.MEDIUM); + + try (MediumEventLoop loop = new MediumEventLoop(null, "pending-medium", + Pauser.balanced(), true, null)) { + loop.addHandler(handler); + assertEquals(0, handler.loopStartedCalled(), "Handler should not start before loop runs"); + } + + assertEquals(0, handler.loopStartedCalled(), "loopStarted should not be called"); + assertEquals(0, handler.actionCalled(), "action should not be called"); + assertEquals(1, handler.closeCalled(), "Handler should be closed when loop closes"); + } + + private static final class RecordingHandler implements EventHandler { + private final HandlerPriority priority; + private final AtomicReference threadRef; + + private RecordingHandler(HandlerPriority priority, AtomicReference threadRef) { + this.priority = priority; + this.threadRef = threadRef; + } + + @Override + public @NotNull HandlerPriority priority() { + return priority; + } + + @Override + public boolean action() throws InvalidEventHandlerException { + threadRef.compareAndSet(null, Thread.currentThread()); + return false; + } + } +} diff --git a/src/test/java/net/openhft/chronicle/threads/MediumEventLoopTest.java b/src/test/java/net/openhft/chronicle/threads/MediumEventLoopTest.java index a5b2359b9..11e6c5c4c 100644 --- a/src/test/java/net/openhft/chronicle/threads/MediumEventLoopTest.java +++ b/src/test/java/net/openhft/chronicle/threads/MediumEventLoopTest.java @@ -152,7 +152,7 @@ void addingHandlerAfterStart(CountingHandler handler) { // Add the handler. eventLoop.addHandler(handler); - Waiters.waitForCondition("Loop started called",() -> (handler.loopStartedCalled() > 0), 5000); + Waiters.waitForCondition("Loop started called", () -> (handler.loopStartedCalled() > 0), 5000); // Check the handler. assertEquals(1, handler.loopStartedCalled()); @@ -305,7 +305,7 @@ void concurrentStartStopDoesNoThrowError() throws ExecutionException, Interrupte ExecutorServiceUtil.shutdownAndWaitForTermination(es); } - private static class NoOpHandler implements EventHandler { + private static final class NoOpHandler implements EventHandler { @Override public boolean action() { diff --git a/src/test/java/net/openhft/chronicle/threads/PauserTimeoutTest.java b/src/test/java/net/openhft/chronicle/threads/PauserTimeoutTest.java index b6dac0d4b..9df03b485 100644 --- a/src/test/java/net/openhft/chronicle/threads/PauserTimeoutTest.java +++ b/src/test/java/net/openhft/chronicle/threads/PauserTimeoutTest.java @@ -55,12 +55,18 @@ public void pausersSupportTimeout() { int timeoutNS = 100_000_000; for (Pauser p : pausersSupportTimeout) { long start = System.nanoTime(); - do try { - p.pause(timeoutNS, TimeUnit.NANOSECONDS); - } catch (TimeoutException e) { - fail(p + " timed out"); - } while (System.nanoTime() < start + timeoutNS / 2); - while (System.nanoTime() < start + timeoutNS * 5 / 4) ; + long halfDeadline = start + timeoutNS / 2; + while (System.nanoTime() < halfDeadline) { + try { + p.pause(timeoutNS, TimeUnit.NANOSECONDS); + } catch (TimeoutException e) { + fail(p + " timed out"); + } + } + long timeoutDeadline = start + timeoutNS * 5 / 4; + while (System.nanoTime() < timeoutDeadline) { + // busy-spin to ensure the timeout window elapses + } try { p.pause(timeoutNS, TimeUnit.NANOSECONDS); } catch (TimeoutException e) { diff --git a/src/test/java/net/openhft/chronicle/threads/ThreadMonitorsTest.java b/src/test/java/net/openhft/chronicle/threads/ThreadMonitorsTest.java new file mode 100644 index 000000000..76876b4ef --- /dev/null +++ b/src/test/java/net/openhft/chronicle/threads/ThreadMonitorsTest.java @@ -0,0 +1,82 @@ +package net.openhft.chronicle.threads; + +import net.openhft.chronicle.core.threads.InvalidEventHandlerException; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.function.LongSupplier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ThreadMonitorsTest { + + @Test + void forThreadLogsWhenEnabled() throws InvalidEventHandlerException { + RecordingConsumer consumer = new RecordingConsumer(); + AtomicBoolean enabled = new AtomicBoolean(true); + ThreadMonitor monitor = ThreadMonitors.forThread( + "loop", + 1_000_000L, + new DeterministicLongSupplier(-5_000_000L, -5_000_000L), + Thread::currentThread, + enabled::get, + consumer + ); + + boolean result = monitor.action(); + + assertFalse(result); + assertEquals(1, consumer.messages.size()); + assertTrue(consumer.messages.get(0).contains("loop")); + } + + @Test + void forThreadSkipsLoggingWhenDisabled() throws InvalidEventHandlerException { + List messages = new ArrayList<>(); + AtomicBoolean enabled = new AtomicBoolean(false); + ThreadMonitor monitor = ThreadMonitors.forThread( + "loop", + 1_000_000L, + new DeterministicLongSupplier(-5_000_000L, -5_000_000L), + Thread::currentThread, + enabled::get, + messages::add + ); + + boolean result = monitor.action(); + + assertFalse(result); + assertTrue(messages.isEmpty()); + } + + private static final class DeterministicLongSupplier implements LongSupplier { + private final long[] values; + private int index; + + DeterministicLongSupplier(long... values) { + this.values = values; + } + + @Override + public long getAsLong() { + if (index >= values.length) { + return values[values.length - 1]; + } + return values[index++]; + } + } + + private static final class RecordingConsumer implements Consumer { + private final List messages = new ArrayList<>(); + + @Override + public void accept(String message) { + messages.add(message); + } + } +} diff --git a/src/test/java/net/openhft/chronicle/threads/VanillaEventLoopTest.java b/src/test/java/net/openhft/chronicle/threads/VanillaEventLoopTest.java index 32409e6fa..3a8f2ddf9 100644 --- a/src/test/java/net/openhft/chronicle/threads/VanillaEventLoopTest.java +++ b/src/test/java/net/openhft/chronicle/threads/VanillaEventLoopTest.java @@ -36,7 +36,7 @@ class VanillaEventLoopTest extends ThreadsTestCommon { @Test void testAddingTwoEventHandlersBeforeStartingLoopIsThreadSafe() { for (int i = 0; i < 10_000; i++) { - try (VanillaEventLoop eventLoop = new VanillaEventLoop(null, "name", Pauser.balanced(), 1000L, true,"", VanillaEventLoop.ALLOWED_PRIORITIES)) { + try (VanillaEventLoop eventLoop = new VanillaEventLoop(null, "name", Pauser.balanced(), 1000L, true, "", VanillaEventLoop.ALLOWED_PRIORITIES)) { CyclicBarrier barrier = new CyclicBarrier(2); IntStream.range(0, 2).parallel() .forEach(ignored -> { @@ -91,7 +91,7 @@ public boolean action() throws InvalidEventHandlerException, InvalidMarshallable } void addingHandlerBeforeStart(CountingHandler handler) { - try (VanillaEventLoop eventLoop = new VanillaEventLoop(null, "name", Pauser.balanced(), 1000L, true, null,VanillaEventLoop.ALLOWED_PRIORITIES)) { + try (VanillaEventLoop eventLoop = new VanillaEventLoop(null, "name", Pauser.balanced(), 1000L, true, null, VanillaEventLoop.ALLOWED_PRIORITIES)) { // Add the handler. eventLoop.addHandler(handler); @@ -134,7 +134,7 @@ void addingDaemonHandlerBeforeStart() { } void addingHandlerAfterStart(CountingHandler handler) { - try (VanillaEventLoop eventLoop = new VanillaEventLoop(null, "name", Pauser.balanced(), 1000L, true, null,VanillaEventLoop.ALLOWED_PRIORITIES)) { + try (VanillaEventLoop eventLoop = new VanillaEventLoop(null, "name", Pauser.balanced(), 1000L, true, null, VanillaEventLoop.ALLOWED_PRIORITIES)) { // Start the loop. eventLoop.start(); @@ -143,7 +143,7 @@ void addingHandlerAfterStart(CountingHandler handler) { // Add the handler. eventLoop.addHandler(handler); - Waiters.waitForCondition("Loop started called",() -> (handler.loopStartedCalled() > 0), 5000); + Waiters.waitForCondition("Loop started called", () -> (handler.loopStartedCalled() > 0), 5000); // Check the handler. assertEquals(1, handler.loopStartedCalled()); @@ -178,7 +178,7 @@ void addingDaemonHandlerAfterStart() { } void throwingHandlerAddedBeforeStart(ThrowingHandler handler) { - try (VanillaEventLoop eventLoop = new VanillaEventLoop(null, "name", Pauser.balanced(), 1000L, true, null,VanillaEventLoop.ALLOWED_PRIORITIES)) { + try (VanillaEventLoop eventLoop = new VanillaEventLoop(null, "name", Pauser.balanced(), 1000L, true, null, VanillaEventLoop.ALLOWED_PRIORITIES)) { expectException(HANDLER_LOOP_STARTED_EXCEPTION_TXT); expectException(HANDLER_LOOP_FINISHED_EXCEPTION_TXT); expectException(HANDLER_CLOSE_EXCEPTION_TXT); @@ -226,7 +226,7 @@ void throwingDaemonHandlerAddedBeforeStart() { } void throwingHandlerAddingAfterStart(ThrowingHandler handler) { - try (VanillaEventLoop eventLoop = new VanillaEventLoop(null, "name", Pauser.balanced(), 1000L, true, null,VanillaEventLoop.ALLOWED_PRIORITIES)) { + try (VanillaEventLoop eventLoop = new VanillaEventLoop(null, "name", Pauser.balanced(), 1000L, true, null, VanillaEventLoop.ALLOWED_PRIORITIES)) { expectException(HANDLER_LOOP_STARTED_EXCEPTION_TXT); expectException(HANDLER_LOOP_FINISHED_EXCEPTION_TXT); expectException(HANDLER_CLOSE_EXCEPTION_TXT); @@ -286,7 +286,7 @@ private void checkEventLoopAlive(VanillaEventLoop eventLoop) { void concurrentStartStopDoesNoThrowError() throws ExecutionException, InterruptedException { ExecutorService es = Executors.newCachedThreadPool(); for (int i = 0; i < 100; i++) { - try (VanillaEventLoop vanillaEventLoop = new VanillaEventLoop(null, "name", Pauser.balanced(), 1000L, true, null,VanillaEventLoop.ALLOWED_PRIORITIES)) { + try (VanillaEventLoop vanillaEventLoop = new VanillaEventLoop(null, "name", Pauser.balanced(), 1000L, true, null, VanillaEventLoop.ALLOWED_PRIORITIES)) { final Future starter = es.submit(vanillaEventLoop::start); final Future stopper = es.submit(vanillaEventLoop::stop); starter.get(); @@ -296,7 +296,7 @@ void concurrentStartStopDoesNoThrowError() throws ExecutionException, Interrupte ExecutorServiceUtil.shutdownAndWaitForTermination(es); } - private static class NoOpHandler implements EventHandler { + private static final class NoOpHandler implements EventHandler { @Override public boolean action() { diff --git a/system.properties b/system.properties index 521b90a28..da588d77d 100644 --- a/system.properties +++ b/system.properties @@ -13,11 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # - # Tracing if resources are closed/released correctly. jvm.resource.tracing=true disable.resource.warning=true - disable.discard.warning=false # for profiling jvm.safepoint.enabled=false diff --git a/systemProperties.adoc b/systemProperties.adoc index 50be8300e..eb782dd8a 100644 --- a/systemProperties.adoc +++ b/systemProperties.adoc @@ -1,4 +1,5 @@ == System Properties + Chronicle Threads reads several system properties at start up. These values tune event loops, pausing strategies, monitoring intervals and disk space checks. All properties may be supplied on the command line with `-D` flags. @@ -6,7 +7,8 @@ All properties may be supplied on the command line with `-D` flags. NOTE: All boolean properties below are read using link:https://javadoc.io/static/net.openhft/chronicle-core/2.23ea13/net/openhft/chronicle/core/Jvm.html#getBoolean-java.lang.String-[net.openhft.chronicle.core.Jvm.getBoolean(java.lang.String)], and so are enabled if either `-Dflag` or `-Dflag=true` or `-Dflag=yes`. === Disk monitoring -[cols=4*, options="header"] + +[cols=4*,options="header"] |=== | Property Key | Default | Description | Java Variable Name (Type) | chronicle.disk.monitor.disable | `false` | Disable the background disk space monitor | _DISABLED_ (boolean) @@ -15,7 +17,8 @@ NOTE: All boolean properties below are read using link:https://javadoc.io/static |=== === Event loops -[cols=4*, options="header"] + +[cols=4*,options="header"] |=== | Property Key | Default | Description | Java Variable Name (Type) | eventloop.accept.mod | 128 | Prevent starvation by inserting new handlers every modulo iteration | _ACCEPT_HANDLER_MOD_COUNT_ (int) @@ -26,14 +29,16 @@ NOTE: All boolean properties below are read using link:https://javadoc.io/static |=== === Pausers -[cols=4*, options="header"] + +[cols=4*,options="header"] |=== | Property Key | Default | Description | Java Variable Name (Type) | pauser.minProcessors | 4 | Minimum number of processors required before busy pausing is used | _MIN_PROCESSORS_ (int) |=== === Monitoring -[cols=4*, options="header"] + +[cols=4*,options="header"] |=== | Property Key | Default | Description | Java Variable Name (Type) | disableLoopBlockMonitor | `false` | Disable loop block monitoring | _ENABLE_LOOP_BLOCK_MONITOR_ (boolean)