Skip to content

Commit 4f0685a

Browse files
committed
Add requirements document for Java Thread Affinity library
1 parent 320d0bf commit 4f0685a

File tree

1 file changed

+297
-0
lines changed

1 file changed

+297
-0
lines changed
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
= Requirements Document: Java Thread Affinity
2+
Author: Gemini AI
3+
Date: 23 May 2025
4+
Version: 1.0
5+
:toc: left
6+
:toclevels: 3
7+
:sectnums:
8+
9+
== 1. Introduction
10+
11+
This document outlines the requirements for the *Java Thread Affinity* library. The primary purpose of this library is to provide Java applications with the capability to control Central Processing Unit (CPU) affinity for their threads. This allows developers to bind specific threads to designated CPU cores, which can lead to performance improvements, especially in latency-sensitive applications, by reducing context switching and improving cache utilisation.
12+
13+
The library aims to offer a cross-platform API, with the most comprehensive support for Linux systems, leveraging Java Native Access (JNA) and, where applicable, Java Native Interface (JNI) for low-level system interactions.
14+
15+
== 2. Scope
16+
17+
The scope of the Java Thread Affinity project includes:
18+
19+
* Providing mechanisms to get and set thread affinity on supported operating systems.
20+
* Offering a CPU locking mechanism (`AffinityLock`) to manage core reservations for threads or entire cores.
21+
* Detecting or allowing specification of the CPU layout (sockets, cores, threads per core).
22+
* Providing a high-resolution timer.
23+
* Supporting inter-process lock checking for CPU core reservations.
24+
* Delivering a thread factory that assigns affinity to newly created threads.
25+
* Packaging the core library and an OSGi-compatible test bundle.
26+
27+
== 3. Definitions, Acronyms, and Abbreviations
28+
29+
* *CPU*: Central Processing Unit
30+
* *JNA*: Java Native Access
31+
* *JNI*: Java Native Interface
32+
* *OS*: Operating System
33+
* *PID*: Process Identifier
34+
* *OSGi*: Open Service Gateway initiative
35+
* *POM*: Project Object Model (Maven)
36+
* *API*: Application Programming Interface
37+
38+
== 4. References
39+
40+
* Project Repository: https://github.com/OpenHFT/Java-Thread-Affinity
41+
* JNA: https://github.com/java-native-access/jna
42+
43+
== 5. Project Overview
44+
45+
The *Java Thread Affinity* library enables fine-grained control over which CPU cores Java threads execute on. This is particularly beneficial for high-performance computing and low-latency applications where minimising jitter and maximising cache efficiency is critical. The library abstracts OS-specific details, providing a unified Java API.
46+
47+
=== 5.1. Purpose
48+
49+
* To allow Java threads to be bound to specific CPU cores.
50+
* To provide tools for understanding and managing CPU topology from within a Java application.
51+
* To offer a high-resolution timing mechanism.
52+
53+
=== 5.2. Benefits
54+
55+
* _Performance Improvement_: Reduced thread migration and context switching.
56+
* _Cache Efficiency_: Better utilisation of CPU caches (L1, L2, L3).
57+
* _Jitter Reduction_: More predictable thread execution times.
58+
59+
== 6. Functional Requirements
60+
61+
=== 6.1. Core Affinity Control (net.openhft.affinity.Affinity)
62+
63+
* *FR1*: The system _shall_ allow setting the affinity of the current thread to a specific CPU core or a set of cores (BitSet).
64+
** `Affinity.setAffinity(BitSet affinity)`
65+
** `Affinity.setAffinity(int cpu)`
66+
* *FR2*: The system _shall_ allow retrieving the current affinity mask of the current thread.
67+
** `Affinity.getAffinity()`
68+
* *FR3*: The system _shall_ allow querying the logical CPU ID the current thread is running on.
69+
** `Affinity.getCpu()`
70+
* *FR4*: The system _shall_ allow retrieving the native process ID of the current Java process.
71+
** `IAffinity.getProcessId()`
72+
* *FR5*: The system _shall_ allow retrieving the native thread ID of the current Java thread.
73+
** `IAffinity.getThreadId()`
74+
** `Affinity.setThreadId()` (to update `Thread.tid` via reflection if available)
75+
76+
=== 6.2. CPU Lock Management (net.openhft.affinity.AffinityLock)
77+
78+
* *FR6.1*: The system _shall_ provide a mechanism to acquire an exclusive lock on an available CPU core for the current thread.
79+
** `AffinityLock.acquireLock()`
80+
** `AffinityLock.acquireLock(boolean bind)`
81+
** `AffinityLock.acquireLock(int cpuId)`
82+
** `AffinityLock.acquireLock(String desc)` (e.g., "last", "last-N", "N", "any", "none", "csv:1,2,3")
83+
* *FR6.2*: The system _shall_ provide a mechanism to acquire an exclusive lock on an entire physical core (including all its logical CPUs/hyper-threads).
84+
** `AffinityLock.acquireCore()`
85+
** `AffinityLock.acquireCore(boolean bind)`
86+
* *FR6.3*: Acquired locks _shall_ be releasable, restoring the thread's affinity to a base state or a defined default.
87+
** `AffinityLock.release()`
88+
** `AffinityLock.close()` (for try-with-resources)
89+
* *FR6.4*: The system _shall_ support affinity strategies for acquiring new locks relative to existing locks (e.g., same core, same socket, different core, different socket).
90+
** `AffinityLock.acquireLock(AffinityStrategy... strategies)`
91+
** `AffinityStrategies` enum: `ANY`, `SAME_CORE`, `SAME_SOCKET`, `DIFFERENT_CORE`, `DIFFERENT_SOCKET`.
92+
* *FR6.5*: The system _shall_ provide a method to dump the current status of all CPU locks managed by the library.
93+
** `AffinityLock.dumpLocks()`
94+
* *FR6.6*: The system _shall_ allow querying if a lock is allocated and bound.
95+
** `AffinityLock.isAllocated()`
96+
** `AffinityLock.isBound()`
97+
98+
=== 6.3. CPU Layout Detection (net.openhft.affinity.CpuLayout)
99+
100+
* *FR7.1*: On Linux, the system _shall_ attempt to automatically detect the CPU layout (sockets, cores per socket, threads per core) by parsing `/proc/cpuinfo`.
101+
** `VanillaCpuLayout.fromCpuInfo()`
102+
* *FR7.2*: The system _shall_ allow applications to programmatically define the CPU layout.
103+
** `AffinityLock.cpuLayout(CpuLayout cpuLayout)`
104+
* *FR7.3*: The CPU layout _shall_ provide information about:
105+
** Total number of logical CPUs: `CpuLayout.cpus()`
106+
** Number of sockets: `CpuLayout.sockets()`
107+
** Cores per socket: `CpuLayout.coresPerSocket()`
108+
** Threads per core: `CpuLayout.threadsPerCore()`
109+
** Mapping a logical CPU ID to its socket, core, and thread ID: `socketId(int)`, `coreId(int)`, `threadId(int)`.
110+
** Hyper-threaded pair for a CPU: `pair(int)`.
111+
112+
=== 6.4. High-Resolution Timer (net.openhft.ticker.Ticker)
113+
114+
* *FR8.1*: The system _shall_ provide a high-resolution time source.
115+
** `Ticker.ticks()` (raw timer ticks)
116+
** `Ticker.nanoTime()` (ticks converted to nanoseconds)
117+
* *FR8.2*: If native JNI components are available and loaded (specifically `libCEInternals.so`), the timer _shall_ attempt to use `rdtsc` (Read Time-Stamp Counter).
118+
** `JNIClock.rdtsc0()`
119+
* *FR8.3*: If JNI-based `rdtsc` is not available, the timer _shall_ fall back to `System.nanoTime()`.
120+
** `SystemClock.INSTANCE`
121+
* *FR8.4*: The timer _shall_ provide methods to convert ticks to nanoseconds and microseconds.
122+
** `ITicker.toNanos(long ticks)`
123+
** `ITicker.toMicros(double ticks)`
124+
125+
=== 6.5. OS-Specific Implementations (net.openhft.affinity.impl)
126+
127+
* *FR9.1*: The system _shall_ provide tailored implementations of `IAffinity` for different operating systems:
128+
** *Linux*: Full affinity control, CPU ID, Process ID, Thread ID via JNA (`LinuxJNAAffinity`, `PosixJNAAffinity`) or JNI (`NativeAffinity`).
129+
** *Windows*: Thread affinity control, Process ID, Thread ID via JNA (`WindowsJNAAffinity`). `getCpu()` returns -1.
130+
** *macOS*: Process ID, Thread ID via JNA (`OSXJNAAffinity`). No affinity modification; `getCpu()` returns -1.
131+
** *Solaris*: Process ID, Thread ID via JNA (`SolarisJNAAffinity`). No affinity modification; `getCpu()` returns -1.
132+
* *FR9.2*: A `NullAffinity` implementation _shall_ be used as a fallback if no suitable native implementation can be loaded or for unsupported OS.
133+
134+
=== 6.6. Affinity Thread Factory (net.openhft.affinity.AffinityThreadFactory)
135+
136+
* *FR10.1*: The system _shall_ provide a `ThreadFactory` that assigns affinity to newly created threads based on specified `AffinityStrategy` rules.
137+
** `new AffinityThreadFactory(String name, AffinityStrategy... strategies)`
138+
* *FR10.2*: If no strategies are provided, `AffinityStrategies.ANY` _shall_ be used by default.
139+
140+
=== 6.7. Inter-Process Lock Checking (net.openhft.affinity.lockchecker)
141+
142+
* *FR11.1*: On Linux, the system _shall_ provide a mechanism to check if a specific CPU core is free or already locked by another process.
143+
** `LockCheck.isCpuFree(int cpu)`
144+
* *FR11.2*: This mechanism _shall_ use file-based locks located in the directory specified by the `java.io.tmpdir` system property.
145+
** `FileLockBasedLockChecker`
146+
* *FR11.3*: The system _shall_ allow obtaining and releasing these inter-process locks for specified CPU IDs.
147+
** `LockChecker.obtainLock(int id, int id2, String metaInfo)`
148+
** `LockChecker.releaseLock(int id)`
149+
* *FR11.4*: The system _shall_ store meta-information (e.g., PID of the locking process) within the lock file and allow its retrieval.
150+
** `LockChecker.getMetaInfo(int id)`
151+
152+
=== 6.8. Native Code Compilation (C/C++)
153+
154+
* *FR12.1*: The system _shall_ include C/C++ source code for native functions required for affinity and timer operations on Linux and macOS.
155+
** `software_chronicle_enterprise_internals_impl_NativeAffinity.cpp` (Linux)
156+
** `software_chronicle_enterprise_internals_impl_NativeAffinity_MacOSX.c` (macOS)
157+
** `net_openhft_ticker_impl_JNIClock.cpp` (for `rdtsc`)
158+
* *FR12.2*: A Makefile _shall_ be provided to compile the native C/C++ code into a shared library (`libCEInternals.so`).
159+
* *FR12.3*: The Java code _shall_ load this native library if available.
160+
** `software.chronicle.enterprise.internals.impl.NativeAffinity.loadAffinityNativeLibrary()`
161+
162+
== 7. Non-Functional Requirements
163+
164+
* *NFR1. Platform Support*:
165+
** *Primary Support*: Linux (full functionality).
166+
** *Partial Support*: Windows (affinity setting, PID/TID, no `getCpu()`).
167+
** *Limited Support*: macOS, Solaris (PID/TID only, no affinity setting or `getCpu()`).
168+
* *NFR2. Dependencies*:
169+
** *JNA*: `net.java.dev.jna:jna`, `net.java.dev.jna:jna-platform`. Version 5.x or higher is recommended for full functionality. The project currently uses version 4.4.0 (as per README, though POMs might show updates).
170+
** *SLF4J API*: `org.slf4j:slf4j-api` for logging.
171+
** *JetBrains Annotations*: `org.jetbrains:annotations` for code quality.
172+
* *NFR3. Performance*: The library _should_ introduce minimal overhead. Native calls _should_ be efficient. The primary goal is to enable performance improvements in the client application.
173+
* *NFR4. Licensing*: The project _shall_ be licensed under the Apache License, Version 2.0.
174+
* *NFR5. Build System*: The project _shall_ use Apache Maven for building and dependency management.
175+
* *NFR6. Language*:
176+
** Core library _shall_ be implemented in Java (1.8+ as per POM).
177+
** Native components _shall_ be implemented in C/C++.
178+
* *NFR7. Usability*:
179+
** The API _should_ be clear and relatively simple to use.
180+
** Javadoc _shall_ be provided for public APIs.
181+
** Example usage _shall_ be available (e.g., in test sources and README).
182+
* *NFR8. Error Handling and Resilience*:
183+
** The library _shall_ degrade gracefully if JNA or native libraries are not available or if an OS does not support certain features (e.g., falling back to `NullAffinity`).
184+
** Errors during native calls _should_ be appropriately logged and/or propagated as exceptions.
185+
* *NFR9. Configuration*:
186+
** Reserved CPUs for the application _shall_ be configurable via the system property `affinity.reserved={hex-mask}`.
187+
** The lock file directory _shall_ default to `java.io.tmpdir` and be overridable by setting this system property.
188+
* *NFR10. OSGi Support*: The `affinity-test` module _shall_ be packaged as an OSGi bundle, demonstrating OSGi compatibility.
189+
* *NFR11. Language Style*: Code and documentation _shall_ use British English, except for established technical US spellings (e.g., `synchronized`).
190+
191+
== 8. System Architecture
192+
193+
=== 8.1. High-Level Architecture
194+
195+
The Java Thread Affinity library is a Java-based system that interfaces with the underlying operating system through JNA (primarily) and JNI (for specific `libCEInternals.so` functionalities). It abstracts OS-specific system calls related to thread affinity, CPU information, and timing.
196+
197+
=== 8.2. Key Components
198+
199+
* *`net.openhft.affinity.Affinity`*: Main public API facade for basic affinity operations.
200+
* *`net.openhft.affinity.IAffinity`*: Interface defining the contract for OS-specific implementations.
201+
** Concrete Implementations: `LinuxJNAAffinity`, `WindowsJNAAffinity`, `OSXJNAAffinity`, `SolarisJNAAffinity`, `PosixJNAAffinity`, `NativeAffinity` (JNI), `NullAffinity`.
202+
* *`net.openhft.affinity.AffinityLock`*: Manages CPU reservations and bindings.
203+
* *`net.openhft.affinity.LockInventory`*: Tracks the state of CPU locks based on `CpuLayout`.
204+
* *`net.openhft.affinity.CpuLayout`*: Interface for CPU topology information.
205+
** `VanillaCpuLayout`: Parses `/proc/cpuinfo` or properties files.
206+
** `NoCpuLayout`: Default layout if detection fails.
207+
* *`net.openhft.affinity.AffinityStrategies`*: Enum defining strategies for selecting CPUs.
208+
* *`net.openhft.affinity.AffinityThreadFactory`*: A `java.util.concurrent.ThreadFactory` that sets affinity for new threads.
209+
* *`net.openhft.ticker.Ticker`*: Provides high-resolution time.
210+
** `JNIClock`: Uses `rdtsc` via JNI.
211+
** `SystemClock`: Uses `System.nanoTime()`.
212+
* *`net.openhft.affinity.lockchecker.LockChecker`*: Interface for inter-process lock management.
213+
** `FileLockBasedLockChecker`: Implementation using file system locks.
214+
* *Native Code (`src/main/c`)*: C/C++ sources for `libCEInternals.so` providing functions like `getAffinity0`, `setAffinity0` (Linux JNI), `rdtsc0`.
215+
216+
=== 8.3. Maven Modules
217+
218+
* *`Java-Thread-Affinity` (Parent POM)*: Aggregates sub-modules.
219+
** Group ID: `net.openhft`
220+
** Artifact ID: `Java-Thread-Affinity`
221+
* *`affinity` (Core Library)*: Contains the main library code, JNA/JNI integrations, and native sources.
222+
** Artifact ID: `affinity`
223+
** Packaging: `bundle` (OSGi compatible)
224+
* *`affinity-test` (Test Module)*: Contains OSGi integration tests and example usage.
225+
** Artifact ID: `affinity-test`
226+
** Packaging: `bundle`
227+
228+
== 9. Native Components (libCEInternals.so)
229+
230+
The library can utilise an optional native shared library, `libCEInternals.so`, for certain operations, primarily on Linux.
231+
232+
* *Purpose*: Provides direct JNI implementations for thread affinity and the `rdtsc` timer.
233+
* *Source Location*: `Java-Thread-Affinity/affinity/src/main/c/`
234+
* *Build*: Compiled using the `Makefile` in the source directory (typically invoked by Maven's `exec-maven-plugin`).
235+
* *Key Native Functions Implemented*:
236+
** `Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getAffinity0`
237+
** `Java_software_chronicle_enterprise_internals_impl_NativeAffinity_setAffinity0`
238+
** `Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getCpu0`
239+
** `Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getProcessId0`
240+
** `Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getThreadId0`
241+
** `Java_net_openhft_ticker_impl_JNIClock_rdtsc0`
242+
* *Platform Specifics*:
243+
** *Linux*: Uses `sched_getaffinity`, `sched_setaffinity`, `sched_getcpu`, `getpid`, `syscall(SYS_gettid)`.
244+
** *macOS*: (Separate C file `software_chronicle_enterprise_internals_impl_NativeAffinity_MacOSX.c`) Uses `pthread_mach_thread_np`, `thread_policy_get`, `thread_policy_set`. Note: JNA implementations are generally preferred on macOS.
245+
* *Loading*: The `NativeAffinity.java` class attempts to load `System.loadLibrary("CEInternals")`.
246+
247+
== 10. API Overview
248+
249+
A brief overview of the primary public classes and interfaces:
250+
251+
* *`net.openhft.affinity.Affinity`*:
252+
** Static utility methods for basic affinity operations: `getAffinity()`, `setAffinity(BitSet)`, `setAffinity(int cpu)`, `getCpu()`, `getThreadId()`.
253+
** Manages selection of the appropriate `IAffinity` implementation.
254+
* *`net.openhft.affinity.AffinityLock`*:
255+
** Manages acquisition and release of CPU locks: `acquireLock()`, `acquireCore()`, `release()`, `close()`.
256+
** Configures CPU layout: `cpuLayout(CpuLayout)`.
257+
** Provides information about reserved CPUs: `BASE_AFFINITY`, `RESERVED_AFFINITY`.
258+
* *`net.openhft.affinity.AffinityStrategies`*:
259+
** Enum defining CPU selection strategies for `AffinityLock`.
260+
* *`net.openhft.affinity.CpuLayout`*:
261+
** Interface to describe the machine's CPU topology.
262+
* *`net.openhft.affinity.IAffinity`*:
263+
** Core interface implemented by OS-specific providers.
264+
* *`net.openhft.ticker.Ticker`*:
265+
** Static utility for accessing high-resolution time: `ticks()`, `nanoTime()`.
266+
* *`net.openhft.affinity.AffinityThreadFactory`*:
267+
** Implements `java.util.concurrent.ThreadFactory` to create threads with specific affinity settings.
268+
269+
== 11. Build and Deployment
270+
271+
* The project is built using Apache Maven.
272+
* The main artifact `net.openhft:affinity` is an OSGi bundle.
273+
* Dependencies are managed via `pom.xml` files, including a `third-party-bom` and `chronicle-bom`.
274+
* The `make-c` profile in `affinity/pom.xml` triggers the compilation of native C code using `make`.
275+
* The `maven-bundle-plugin` is used to generate OSGi manifest information.
276+
* The `maven-scm-publish-plugin` is configured for publishing Javadoc to `gh-pages`.
277+
278+
== 12. Testing
279+
280+
The project includes a comprehensive suite of tests:
281+
282+
* *Unit Tests*: Located in `affinity/src/test/java/`.
283+
** `NativeAffinityTest`, `JnaAffinityTest`: Test core JNI/JNA functionalities.
284+
** `AffinityLockTest`: Tests `AffinityLock` logic, including descriptions and inter-thread lock acquisition.
285+
** `VanillaCpuLayoutTest`, `VanillaCpuLayoutPropertiesParseTest`: Test parsing of `cpuinfo` files and properties files for CPU layout.
286+
** `TickerTest`, `JNIClockTest`: Test timer implementations.
287+
** `LockCheckTest`, `FileLockLockCheckTest`: Test inter-process lock checking.
288+
** `MultiProcessAffinityTest`: Tests affinity locking behavior across multiple Java processes.
289+
* *OSGi Bundle Tests*: Located in `affinity-test/src/test/java/net/openhft/affinity/osgi/`.
290+
** `OSGiBundleTest`: Verifies bundle activation and package exports in an OSGi environment using Pax Exam.
291+
* *Test Resources*: Includes sample `cpuinfo` files for various architectures and corresponding properties files to test layout parsing.
292+
** `affinity/src/test/resources/`
293+
* *Test Infrastructure*:
294+
** `BaseAffinityTest`: Provides common setup for tests, including temporary folder management for lock files.
295+
** `chronicle-test-framework`: Used for some test utilities, like `JavaProcessBuilder` for multi-process tests.
296+
297+
The tests cover various aspects including basic affinity setting, CPU layout parsing, lock management, multi-threading scenarios, multi-process contention, and OSGi integration.

0 commit comments

Comments
 (0)