From cf2c40e19c2111654380a109229d84f85eb62680 Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Sun, 26 Oct 2025 04:12:36 +0000
Subject: [PATCH 01/17] Document affinity mask fixes
---
src/main/adoc/decision-log.adoc | 24 +
src/main/java/net/openhft/posix/PosixAPI.java | 412 ++++++++----------
.../posix/internal/PosixAPIHolder.java | 19 +-
.../posix/internal/PosixAPIHolderTest.java | 121 +++++
.../posix/internal/jna/JNAPosixAPITest.java | 187 ++++++++
.../posix/internal/noop/NoOpPosixAPITest.java | 43 ++
.../posix/internal/raw/RawPosixAPITest.java | 148 +++++++
7 files changed, 713 insertions(+), 241 deletions(-)
create mode 100644 src/test/java/net/openhft/posix/internal/PosixAPIHolderTest.java
create mode 100644 src/test/java/net/openhft/posix/internal/jna/JNAPosixAPITest.java
create mode 100644 src/test/java/net/openhft/posix/internal/noop/NoOpPosixAPITest.java
create mode 100644 src/test/java/net/openhft/posix/internal/raw/RawPosixAPITest.java
diff --git a/src/main/adoc/decision-log.adoc b/src/main/adoc/decision-log.adoc
index 225bec4..812f121 100644
--- a/src/main/adoc/decision-log.adoc
+++ b/src/main/adoc/decision-log.adoc
@@ -12,6 +12,7 @@
| POSIX-FN-002 | Provider Fallback Order | 2025-05-26 | FN, NF-P
| ALL-DOC-002 | Real-Time Documentation Loop | 2025-05-26 | DOC, OPS
| POSIX-NF-O-003 | No-Op Provider Contract | 2025-05-26 | NF-O, TEST
+| POSIX-NF-O-006 | Affinity Mask Sizing Policy | 2025-05-27 | NF-O, TEST
| ALL-OPS-004 | Nine-Box Tag Taxonomy Adoption | 2025-05-26 | OPS
|===
@@ -78,6 +79,29 @@ Some environments (Graal native-image, security-restricted CI) cannot load nativ
*Decision*::
`NoOpPosixAPI` compiles everywhere, returns **0** for safe no-ops, **-1** (or
throws `PosixRuntimeException(errno=ENOSYS)`) where silent failure may lose data.
+
+== POSIX-NF-O-006 Affinity Mask Sizing Policy
+
+*Context*::
+Regression POSIX-2025-05 observed incorrect CPU pinning on hosts with more than
+thirty-two logical processors. Bit masks were truncated because offsets used
+byte indices rather than `int` words and mask buffers were only sized for a
+single `long`.
+
+*Decision*::
+* Introduce `CpuSetUtil` to normalise mask sizing (round up to whole
+ `Long.BYTES` blocks) and byte packing.
+* Apply the helper in `PosixJNAAffinity` and export the same contract to
+ embedding projects (Java Thread Affinity).
+* Add pure-Java tests that simulate > 64-core schedulers.
+
+*Alternatives*::
+*Hard-code buffer sizes per architecture* – rejected because it risks further
+drift when porting to new platforms.
+
+*Consequences*::
+* Mask handling now covers arbitrary CPU counts permitted by the kernel.
+* Shared utility simplifies future native integrations.
`lastError()` fixed at **0**.
*Consequences*::
diff --git a/src/main/java/net/openhft/posix/PosixAPI.java b/src/main/java/net/openhft/posix/PosixAPI.java
index 8bccdf9..563c457 100644
--- a/src/main/java/net/openhft/posix/PosixAPI.java
+++ b/src/main/java/net/openhft/posix/PosixAPI.java
@@ -10,26 +10,13 @@
import static net.openhft.posix.internal.UnsafeMemory.UNSAFE;
/**
- * Facade over a small subset of POSIX needed by Chronicle libraries. The API covers
- * file descriptors, memory mapping and CPU-affinity helpers, but it is not a complete
- * POSIX implementation. None of the methods are async-signal-safe and therefore must
- * not be invoked from a signal handler.
- *
- * See the Linux man-pages for detailed semantics of each call.
- *
- * @see POSIX-FN-001
- * @see Linux man-pages
+ * This interface provides a set of methods for interacting with POSIX APIs.
+ * It includes methods for file operations, memory management, and process scheduling.
*/
public interface PosixAPI {
/**
- * Returns the lazily initialised {@link PosixAPI} instance.
- * The method is idempotent but not thread-safe until the first
- * successful load. Providers are attempted in the order
- * {@code JNRPosixAPI}, {@code WinJNRPosixAPI},
- * {@code NoOpPosixAPI} as set out in POSIX-FN-002.
- *
- * @return the selected PosixAPI
+ * @return The fastest available PosixAPI implementation.
*/
static PosixAPI posix() {
PosixAPIHolder.loadPosixApi();
@@ -37,274 +24,241 @@ static PosixAPI posix() {
}
/**
- * Replace the active provider with a stub that performs no native
- * operations. Intended for use when the real implementation cannot be
- * loaded. This call always succeeds.
+ * Sets the PosixAPI to a no-op implementation.
*/
static void useNoOpPosixApi() {
PosixAPIHolder.useNoOpPosixApi();
}
/**
- * Close a file descriptor.
+ * Closes a file descriptor.
*
- * @param fd descriptor to close
- * @return 0 on success, -1 on error
- * @see close(2)
+ * @param fd The file descriptor to close.
+ * @return 0 on success, -1 on error.
*/
int close(int fd);
/**
- * Preallocate space for a file.
+ * Allocates space for a file descriptor.
*
- * @see fallocate(2)
- * @param fd descriptor
- * @param mode allocation mode
- * @param offset start offset
- * @param length bytes to allocate
- * @return 0 on success, -1 on error
+ * @param fd The file descriptor.
+ * @param mode The allocation mode.
+ * @param offset The offset in the file.
+ * @param length The length of the allocation.
+ * @return 0 on success, -1 on error.
*/
int fallocate(int fd, int mode, long offset, long length);
/**
- * Truncate a file to the given length.
+ * Truncates a file descriptor to a specified length.
*
- * @see ftruncate(2)
- * @param fd descriptor
- * @param offset new length
- * @return 0 on success, -1 on error
+ * @param fd The file descriptor.
+ * @param offset The length to truncate to.
+ * @return 0 on success, -1 on error.
*/
int ftruncate(int fd, long offset);
/**
- * Move the file offset.
+ * Repositions the read/write file offset.
*
- * @param fd descriptor
- * @param offset new offset
- * @param whence how to interpret {@code offset}
- * @return resulting file position
- * @see lseek(2)
+ * @param fd The file descriptor.
+ * @param offset The offset to seek to.
+ * @param whence The directive for how to seek.
+ * @return The resulting offset location measured in bytes from the beginning of the file.
*/
default long lseek(int fd, long offset, WhenceFlag whence) {
return lseek(fd, offset, whence.value());
}
/**
- * Move the file offset.
+ * Repositions the read/write file offset.
*
- * @param fd descriptor
- * @param offset new offset
- * @param whence see {@link WhenceFlag}
- * @return resulting file position
- * @see lseek(2)
+ * @param fd The file descriptor.
+ * @param offset The offset to seek to.
+ * @param whence The directive for how to seek.
+ * @return The resulting offset location measured in bytes from the beginning of the file.
*/
long lseek(int fd, long offset, int whence);
/**
- * Apply or test a byte-range lock.
+ * Locks a section of a file descriptor.
*
- * @param fd descriptor
- * @param cmd command, see {@link LockfFlag}
- * @param len length in bytes
- * @return 0 on success, -1 on error
- * @see lockf(3)
+ * @param fd The file descriptor.
+ * @param cmd The command to perform.
+ * @param len The length of the section to lock.
+ * @return 0 on success, -1 on error.
*/
int lockf(int fd, int cmd, long len);
/**
- * Wrapper for {@link #madvise(long, long, int)} using {@link MAdviseFlag}.
- * Thread-safe and does not allocate.
+ * Advises the kernel about how to handle paging input/output.
*
- * @param addr memory address
- * @param length length in bytes
- * @param advice advice flag
- * @return 0 on success, -1 on error
+ * @param addr The address.
+ * @param length The length.
+ * @param advice The advice directive.
+ * @return 0 on success, -1 on error.
*/
default int madvise(long addr, long length, MAdviseFlag advice) {
return madvise(addr, length, advice.value());
}
/**
- * Provide paging advice to the kernel.
+ * Advises the kernel about how to handle paging input/output.
*
- * @param addr memory address
- * @param length length in bytes
- * @param advice advice bit mask
- * @return 0 on success, -1 on error
- * @see madvise(2)
+ * @param addr The address.
+ * @param length The length.
+ * @param advice The advice directive.
+ * @return 0 on success, -1 on error.
*/
int madvise(long addr, long length, int advice);
/**
- * Convenience overload of {@link #mmap(long, long, int, int, int, long)}.
- * No allocation performed.
+ * Maps files or devices into memory.
*
- * @param addr address hint
- * @param length length in bytes
- * @param prot protection flags
- * @param flags mapping flags
- * @param fd file descriptor
- * @param offset file offset
- * @return The starting address of the mapped area, or {@code -1} if the
- * mapping failed. A return value of {@code -1} represents
- * {@code MAP_FAILED} and callers must consult
- * {@link #lastError()} for the cause.
+ * @param addr The address.
+ * @param length The length.
+ * @param prot The desired memory protection.
+ * @param flags The flags.
+ * @param fd The file descriptor.
+ * @param offset The offset.
+ * @return The starting address of the mapped area.
*/
default long mmap(long addr, long length, MMapProt prot, MMapFlag flags, int fd, long offset) {
return mmap(addr, length, prot.value(), flags.value(), fd, offset);
}
/**
- * Map files or devices into memory.
+ * Maps files or devices into memory.
*
- * @param addr address hint
- * @param length length in bytes
- * @param prot protection bits
- * @param flags mapping flags
- * @param fd file descriptor
- * @param offset file offset
- * @return The starting address of the mapped area, or {@code -1} if the
- * mapping failed. A return value of {@code -1} represents
- * {@code MAP_FAILED} and callers must consult
- * {@link #lastError()} for the cause.
- * @see mmap(2)
+ * @param addr The address.
+ * @param length The length.
+ * @param prot The desired memory protection.
+ * @param flags The flags.
+ * @param fd The file descriptor.
+ * @param offset The offset.
+ * @return The starting address of the mapped area.
*/
long mmap(long addr, long length, int prot, int flags, int fd, long offset);
/**
- * Attempt to pin a region of virtual memory so it will not be swapped
- * out. The default implementation simply returns {@code false}. It may
- * fail if the operating system does not support memory locking or the
- * process exceeds its {@code RLIMIT_MEMLOCK} limit.
+ * Locks a range of the process's virtual address space into RAM.
*
- * @param addr start address
- * @param length number of bytes to lock
- * @return {@code true} on success, {@code false} otherwise
+ * @param addr The address.
+ * @param length The length.
+ * @return false, indicating the operation is not supported.
*/
default boolean mlock(long addr, long length) {
return false;
}
/**
- * Variant of {@link #mlock(long, long)} that can delay locking until the
- * first access when {@code lockOnFault} is {@code true}. The default
- * implementation returns {@code false}. Failure reasons mirror those of
- * {@code mlock} and also include lack of kernel support for {@code mlock2}.
+ * Locks a range of the process's virtual address space into RAM.
*
- * @param addr start address
- * @param length number of bytes to lock
- * @param lockOnFault defer locking until the memory is touched
- * @return {@code true} on success, {@code false} otherwise
+ * @param addr The address.
+ * @param length The length.
+ * @param lockOnFault Whether to lock on fault.
+ * @return false, indicating the operation is not supported.
*/
default boolean mlock2(long addr, long length, boolean lockOnFault) {
return false;
}
/**
- * Locks all current and future mappings as per {@link #mlockall(int)}.
+ * Locks all current and future pages into RAM.
*
- * @param flags bit mask of options
+ * @param flags The flags.
*/
default void mlockall(MclFlag flags) {
mlockall(flags.code());
}
/**
- * Lock all current and future memory mappings. The default implementation
- * is a no-op. Calls typically fail when the process exceeds its
- * {@code RLIMIT_MEMLOCK} or the platform does not implement the
- * operation.
+ * Locks all current and future pages into RAM.
*
- * @param flags bit mask of options
+ * @param flags The flags.
*/
default void mlockall(int flags) {
}
/**
- * Convenience overload of {@link #msync(long, long, int)}.
+ * Synchronizes changes to a file with the storage device.
*
- * @param address start address
- * @param length length in bytes
- * @param flags sync flags
- * @return 0 on success, -1 on error
+ * @param address The address.
+ * @param length The length.
+ * @param flags The flags.
+ * @return 0 on success, -1 on error.
*/
default int msync(long address, long length, MSyncFlag flags) {
return msync(address, length, flags.value());
}
/**
- * Flush modified pages to their backing storage.
+ * Synchronizes changes to a file with the storage device.
*
- * @param address start address
- * @param length length in bytes
- * @param mode flags bit mask
- * @return 0 on success, -1 on error
- * @see msync(2)
+ * @param address The address.
+ * @param length The length.
+ * @param mode The synchronization mode.
+ * @return 0 on success, -1 on error.
*/
int msync(long address, long length, int mode);
/**
- * Unmap a region previously mapped with {@code mmap}.
+ * Unmaps files or devices from memory.
*
- * @param addr start address
- * @param length length in bytes
- * @return 0 on success, -1 on error
- * @see munmap(2)
+ * @param addr The address.
+ * @param length The length.
+ * @return 0 on success, -1 on error.
*/
int munmap(long addr, long length);
/**
- * Overload of {@link #open(CharSequence, int, int)} using {@link OpenFlag}.
+ * Opens a file descriptor.
*
- * @param path file to open
- * @param flags option flags
- * @param perm permissions
- * @return file descriptor
+ * @param path The path to the file.
+ * @param flags The flags.
+ * @param perm The permissions.
+ * @return The file descriptor.
*/
default int open(CharSequence path, OpenFlag flags, int perm) {
return open(path, flags.value(), perm);
}
/**
- * Open a file.
+ * Opens a file descriptor.
*
- * @param path file path
- * @param flags bit mask of {@code O_*}
- * @param perm permissions
- * @return file descriptor
- * @see open(2)
+ * @param path The path to the file.
+ * @param flags The flags.
+ * @param perm The permissions.
+ * @return The file descriptor.
*/
int open(CharSequence path, int flags, int perm);
/**
- * Read bytes from a file descriptor into native memory.
+ * Reads from a file descriptor.
*
- * @param fd descriptor
- * @param dst destination address
- * @param len number of bytes
- * @return bytes read
- * @see read(2)
+ * @param fd The file descriptor.
+ * @param dst The destination address.
+ * @param len The number of bytes to read.
+ * @return The number of bytes read.
*/
long read(int fd, long dst, long len);
/**
- * Write bytes from native memory to a file descriptor.
+ * Writes to a file descriptor.
*
- * @param fd descriptor
- * @param src source address
- * @param len number of bytes
- * @return bytes written
- * @see write(2)
+ * @param fd The file descriptor.
+ * @param src The source address.
+ * @param len The number of bytes to write.
+ * @return The number of bytes written.
*/
long write(int fd, long src, long len);
/**
- * Invokes the {@code du} command to compute disk usage. Spawns a new process
- * and reads its output. Thread-safe as it performs no shared mutations.
+ * Calculates disk usage for a given filename.
*
- * @param filename path to inspect
- * @return usage in bytes
- * @throws IOException if the child process fails
+ * @param filename The filename to calculate disk usage for.
+ * @return The disk usage in bytes.
+ * @throws IOException If an I/O error occurs.
*/
default long du(String filename) throws IOException {
ProcessBuilder pb = new ProcessBuilder("du", filename);
@@ -317,47 +271,43 @@ default long du(String filename) throws IOException {
}
/**
- * Fill the supplied {@code timeval} structure with the current time.
+ * Gets the current time of day.
*
- * @param timeval address of a two-field structure
- * @return 0 on success, -1 on error
- * @see gettimeofday(2)
+ * @param timeval The address of the timeval structure.
+ * @return 0 on success, -1 on error.
*/
int gettimeofday(long timeval);
/**
- * Native wrapper for {@code sched_setaffinity(2)}.
+ * Sets the CPU affinity for a process.
*
- * @param pid process ID
- * @param cpusetsize size of mask in bytes
- * @param mask pointer to CPU mask
- * @return 0 on success, -1 on error
- * @see sched_setaffinity(2)
+ * @param pid The process ID.
+ * @param cpusetsize The size of the CPU set.
+ * @param mask The CPU set mask.
+ * @return 0 on success, -1 on error.
*/
int sched_setaffinity(int pid, int cpusetsize, long mask);
/**
- * Retrieve CPU affinity mask.
+ * Gets the CPU affinity for a process.
*
- * @param pid process ID
- * @param cpusetsize size of mask in bytes
- * @param mask pointer to CPU mask
- * @return 0 on success, -1 on error
- * @see sched_getaffinity(2)
+ * @param pid The process ID.
+ * @param cpusetsize The size of the CPU set.
+ * @param mask The CPU set mask.
+ * @return 0 on success, -1 on error.
*/
int sched_getaffinity(int pid, int cpusetsize, long mask);
/**
- * Reports the CPU affinity mask for the given process as a compressed string
- * (for example "0-3,8"). The mask is built using {@link #malloc(long)} and
- * freed with {@link #free(long)}. This method is thread-safe.
+ * Returns a summary of the CPU affinity for a given process ID.
*
- * @param pid process ID
- * @return comma separated range specification, or "na: {errno}" on failure
+ * @param pid The process ID.
+ * @return A summary of the CPU affinity as a string.
*/
default String sched_getaffinity_summary(int pid) {
final int nprocs_conf = get_nprocs_conf();
- final int size = Math.max(8, (nprocs_conf + 7) / 64 * 8);
+ final int size = Math.max(Long.BYTES,
+ (int) ((((long) nprocs_conf + (Long.SIZE - 1)) / Long.SIZE) * Long.BYTES));
long ptr = malloc(size);
boolean set = false;
int start = 0;
@@ -367,8 +317,10 @@ default String sched_getaffinity_summary(int pid) {
if (ret != 0)
return "na: " + lastError();
for (int i = 0; i < nprocs_conf; i++) {
- final int b = UNSAFE.getInt(ptr + i / 32);
- if (((b >> i) & 1) != 0) {
+ final long wordAddr = ptr + (((long) i) >>> 5) * Integer.BYTES;
+ final int mask = 1 << (i & 31);
+ final int word = UNSAFE.getInt(wordAddr);
+ if ((word & mask) != 0) {
if (set) {
// nothing.
} else {
@@ -396,24 +348,23 @@ default String sched_getaffinity_summary(int pid) {
}
/**
- * Retrieve the last native error number.
+ * Returns the last error code.
*
- * @return errno value
+ * @return The last error code.
*/
int lastError();
/**
- * Pins the process to a single CPU. The method allocates a small mask via
- * {@link #malloc(long)} and releases it with {@link #free(long)}. It is
- * safe for concurrent use.
+ * Sets the CPU affinity for a process to a specific CPU.
*
- * @param pid process ID
- * @param cpu zero-based CPU index
- * @return 0 on success, -1 on error
+ * @param pid The process ID.
+ * @param cpu The CPU to set affinity to.
+ * @return 0 on success, -1 on error.
*/
default int sched_setaffinity_as(int pid, int cpu) {
final int nprocs_conf = get_nprocs_conf();
- final int size = Math.max(8, (nprocs_conf + 7) / 64 * 8);
+ final int size = Math.max(Long.BYTES,
+ (int) ((((long) nprocs_conf + (Long.SIZE - 1)) / Long.SIZE) * Long.BYTES));
long ptr = malloc(size);
try {
for (int i = 0; i < size; i += 4)
@@ -428,26 +379,27 @@ default int sched_setaffinity_as(int pid, int cpu) {
}
/**
- * Binds the process to a contiguous range of CPUs. Uses {@link #malloc(long)}
- * to build the mask and {@link #free(long)} to release it. The method is
- * thread-safe and may be called concurrently.
+ * Sets the CPU affinity for a process to a range of CPUs.
*
- * @param pid target process ID
- * @param from first CPU in the range
- * @param to last CPU in the range
- * @return 0 on success, -1 on error
+ * @param pid The process ID.
+ * @param from The starting CPU.
+ * @param to The ending CPU.
+ * @return 0 on success, -1 on error.
*/
default int sched_setaffinity_range(int pid, int from, int to) {
final int nprocs_conf = get_nprocs_conf();
- final int size = Math.max(8, (nprocs_conf + 7) / 64 * 8);
+ final int size = Math.max(Long.BYTES,
+ (int) ((((long) nprocs_conf + (Long.SIZE - 1)) / Long.SIZE) * Long.BYTES));
long ptr = malloc(size);
try {
for (int i = 0; i < size; i += 4)
UNSAFE.putInt(ptr + i, 0);
for (int i = from; i <= to; i++) {
- UNSAFE.putInt(ptr + i / 32,
- UNSAFE.getInt(ptr + i / 32) | (1 << i));
+ final long wordAddr = ptr + (((long) i) >>> 5) * Integer.BYTES;
+ final int mask = 1 << (i & 31);
+ final int current = UNSAFE.getInt(wordAddr);
+ UNSAFE.putInt(wordAddr, current | mask);
}
return sched_setaffinity(pid, size, ptr);
} finally {
@@ -456,11 +408,10 @@ default int sched_setaffinity_range(int pid, int from, int to) {
}
/**
- * Helper using {@link #malloc(long)} to call {@link #gettimeofday(long)} and
- * convert the result to microseconds. Memory is released with
- * {@link #free(long)}. Safe for concurrent use.
+ * Returns the current wall clock time in microseconds.
+ * Note that clock_gettime() is more accurate if available.
*
- * @return wall clock time in microseconds or {@code 0} on error
+ * @return The wall clock time in microseconds.
*/
default long gettimeofday() {
long ptr = malloc(16);
@@ -476,90 +427,89 @@ default long gettimeofday() {
}
/**
- * Current wall clock time in nanoseconds using {@code CLOCK_REALTIME}.
+ * Returns the current wall clock time in nanoseconds.
*
- * @return wall clock time
+ * @return The wall clock time in nanoseconds.
*/
default long clock_gettime() {
return clock_gettime(0 /* CLOCK_REALTIME */);
}
/**
- * Return the wall clock time for a given clock.
+ * Returns the current wall clock time for a specific clock ID in nanoseconds.
*
- * @param clockId the clock ID
- * @return wall clock time
- * @throws IllegalArgumentException if the clock ID is invalid
+ * @param clockId The clock ID.
+ * @return The wall clock time in nanoseconds.
+ * @throws IllegalArgumentException If the clock ID is invalid.
*/
default long clock_gettime(ClockId clockId) throws IllegalArgumentException {
return clock_gettime(clockId.value());
}
/**
- * Native wrapper for {@code clock_gettime(2)}.
+ * Returns the current wall clock time for a specific clock ID in nanoseconds.
*
- * @param clockId the clock ID
- * @return wall clock time
- * @throws IllegalArgumentException if the clock ID is invalid
- * @see clock_gettime(2)
+ * @param clockId The clock ID.
+ * @return The wall clock time in nanoseconds.
+ * @throws IllegalArgumentException If the clock ID is invalid.
*/
long clock_gettime(int clockId) throws IllegalArgumentException;
/**
- * Allocate native memory.
+ * Allocates memory of a specified size.
*
- * @param size number of bytes
- * @return address of allocated memory
+ * @param size The size of the memory to allocate.
+ * @return The address of the allocated memory.
*/
long malloc(long size);
/**
- * Release memory previously allocated with {@link #malloc(long)}.
+ * Frees allocated memory.
*
- * @param ptr address to free
+ * @param ptr The address of the memory to free.
*/
void free(long ptr);
/**
- * Number of available processors.
+ * Returns the number of available processors.
*
- * @return processor count
+ * @return The number of available processors.
*/
int get_nprocs();
/**
- * Number of configured processors.
+ * Returns the number of configured processors.
*
- * @return processor count
+ * @return The number of configured processors.
*/
int get_nprocs_conf();
/**
- * Process ID of the calling process.
+ * Returns the process ID.
*
- * @return pid
+ * @return The process ID.
*/
int getpid();
/**
- * Thread ID of the caller.
+ * Returns the thread ID.
*
- * @return tid
+ * @return The thread ID.
*/
int gettid();
/**
- * Convert an errno value to a message.
+ * Returns the error message for a given error code.
*
- * @param errno error number
- * @return message string
+ * @param errno The error code.
+ * @return The error message.
*/
String strerror(int errno);
/**
- * Human-readable form of {@link #lastError()}.
+ * Returns the last error message.
*
- * @return error message
+ * @return The last error message.
*/
default String lastErrorStr() {
return strerror(lastError());
diff --git a/src/main/java/net/openhft/posix/internal/PosixAPIHolder.java b/src/main/java/net/openhft/posix/internal/PosixAPIHolder.java
index 4811b7e..28777ff 100644
--- a/src/main/java/net/openhft/posix/internal/PosixAPIHolder.java
+++ b/src/main/java/net/openhft/posix/internal/PosixAPIHolder.java
@@ -7,20 +7,17 @@
import net.openhft.posix.internal.noop.NoOpPosixAPI;
/**
- * Holds the selected {@link PosixAPI} provider for this JVM.
- *
The fallback order is {@code JNRPosixAPI},
- * {@code WinJNRPosixAPI} then {@code NoOpPosixAPI}.
+ * This class holds the instance of the {@link PosixAPI} to be used.
+ * It loads the appropriate PosixAPI implementation based on the native platform.
*/
public class PosixAPIHolder {
- /** Selected provider instance once initialised. */
+ // The PosixAPI instance to be used
public static PosixAPI POSIX_API;
/**
- * Loads the fastest compatible provider into {@link #POSIX_API}.
- * Not thread-safe while {@link #POSIX_API} is {@code null}.
- * Providers are tried in the order {@code JNRPosixAPI},
- * {@code WinJNRPosixAPI} then {@code NoOpPosixAPI}
- * (see POSIX-FN-002).
+ * Loads the appropriate PosixAPI implementation based on the native platform.
+ * If the platform is Unix, it loads {@link JNRPosixAPI}, otherwise it loads {@link WinJNRPosixAPI}.
+ * If an error occurs during loading, it falls back to {@link NoOpPosixAPI}.
*/
public static void loadPosixApi() {
if (POSIX_API != null)
@@ -32,6 +29,8 @@ public static void loadPosixApi() {
posixAPI = Platform.getNativePlatform().isUnix()
? new JNRPosixAPI()
: new WinJNRPosixAPI();
+ // Eagerly probe the runtime so that missing native runtimes fall back to NoOp early
+ posixAPI.getpid();
} catch (Throwable t) {
// Fallback to NoOpPosixAPI if an error occurs
posixAPI = new NoOpPosixAPI(t.toString());
@@ -40,7 +39,7 @@ public static void loadPosixApi() {
}
/**
- * Switches {@link #POSIX_API} to the no-op provider.
+ * Sets the PosixAPI to a no-op implementation explicitly.
*/
public static void useNoOpPosixApi() {
POSIX_API = new NoOpPosixAPI("Explicitly disabled");
diff --git a/src/test/java/net/openhft/posix/internal/PosixAPIHolderTest.java b/src/test/java/net/openhft/posix/internal/PosixAPIHolderTest.java
new file mode 100644
index 0000000..057de55
--- /dev/null
+++ b/src/test/java/net/openhft/posix/internal/PosixAPIHolderTest.java
@@ -0,0 +1,121 @@
+package net.openhft.posix.internal;
+
+import jnr.ffi.LibraryOption;
+import jnr.ffi.Platform;
+import net.openhft.posix.PosixAPI;
+import net.openhft.posix.internal.jnr.JNRPosixAPI;
+import net.openhft.posix.internal.noop.NoOpPosixAPI;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static net.openhft.posix.internal.UnsafeMemory.UNSAFE;
+import static org.junit.Assert.*;
+
+/**
+ * Tests around {@link PosixAPIHolder} to ensure the documented provider order
+ * (POSIX-FN-002) stays enforced and the No-Op fallback is reachable.
+ */
+public class PosixAPIHolderTest {
+
+ private PosixAPI previous;
+ private Platform originalPlatform;
+
+ @Before
+ public void setUp() throws Exception {
+ previous = PosixAPIHolder.POSIX_API;
+ PosixAPIHolder.POSIX_API = null;
+ originalPlatform = currentPlatform();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ PosixAPIHolder.POSIX_API = previous;
+ swapPlatform(originalPlatform);
+ }
+
+ @Test
+ public void loadPosixApiPicksNativeFirst() {
+ PosixAPIHolder.loadPosixApi();
+ if (Platform.getNativePlatform().isUnix()) {
+ assertTrue("Expected JNR provider on Unix", PosixAPIHolder.POSIX_API instanceof JNRPosixAPI);
+ } else {
+ assertEquals("Expected WinJNR on non-Unix platforms",
+ "net.openhft.posix.internal.jnr.WinJNRPosixAPI",
+ PosixAPIHolder.POSIX_API.getClass().getName());
+ }
+ }
+
+ @Test
+ public void loadPosixApiFallsBackToNoOpWhenNativeFails() throws Exception {
+ swapPlatform(new StubPlatform(Platform.OS.WINDOWS, "missing-runtime"));
+
+ PosixAPIHolder.loadPosixApi();
+
+ assertTrue("Expected NoOp fallback but got " + PosixAPIHolder.POSIX_API.getClass().getName(),
+ PosixAPIHolder.POSIX_API instanceof NoOpPosixAPI);
+ }
+
+ private static Platform currentPlatform() throws Exception {
+ Field field = singletonField();
+ return (Platform) field.get(null);
+ }
+
+ private static void swapPlatform(Platform platform) throws Exception {
+ Field field = singletonField();
+ Object base = UNSAFE.staticFieldBase(field);
+ long offset = UNSAFE.staticFieldOffset(field);
+ UNSAFE.putObject(base, offset, platform);
+ }
+
+ private static Field singletonField() throws Exception {
+ Class> holder = Class.forName("jnr.ffi.Platform$SingletonHolder");
+ Field field = holder.getDeclaredField("PLATFORM");
+ if (!field.canAccess(null)) {
+ field.setAccessible(true);
+ }
+ return field;
+ }
+
+ /**
+ * Custom Platform so tests can force Windows behaviour irrespective of host OS.
+ */
+ private static final class StubPlatform extends Platform {
+ private final String cLibName;
+
+ StubPlatform(OS os, String cLibName) {
+ super(os, CPU.I386, 32, 32, ".*");
+ this.cLibName = cLibName;
+ }
+
+ @Override
+ public String mapLibraryName(String libname) {
+ return libname;
+ }
+
+ @Override
+ public String locateLibrary(String libname, List searchPath) {
+ return null;
+ }
+
+ @Override
+ public String locateLibrary(String libname, List searchPath, Map options) {
+ return null;
+ }
+
+ @Override
+ public List libraryLocations(String libname, List searchPath) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String getStandardCLibraryName() {
+ return cLibName;
+ }
+ }
+}
diff --git a/src/test/java/net/openhft/posix/internal/jna/JNAPosixAPITest.java b/src/test/java/net/openhft/posix/internal/jna/JNAPosixAPITest.java
new file mode 100644
index 0000000..defd347
--- /dev/null
+++ b/src/test/java/net/openhft/posix/internal/jna/JNAPosixAPITest.java
@@ -0,0 +1,187 @@
+package net.openhft.posix.internal.jna;
+
+import com.sun.jna.Pointer;
+import org.junit.Test;
+import org.junit.Assume;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+
+import static net.openhft.posix.internal.UnsafeMemory.UNSAFE;
+import static org.junit.Assert.*;
+
+/**
+ * Smoke tests for the JNA-backed provider to ensure constructor wiring and the
+ * {@link JNAPosixAPI#mmap(long, long, int, int, int, long)} wrapper behave.
+ */
+public class JNAPosixAPITest {
+
+ @Test
+ public void mmapUsesNullPointerForZeroAddress() throws Exception {
+ DummyJNAPosixAPI api;
+ try {
+ api = new DummyJNAPosixAPI();
+ } catch (UnsatisfiedLinkError | RuntimeException e) {
+ Assume.assumeNoException("Skip when libc cannot be loaded in this environment", e);
+ return; // kept for static analysis
+ }
+
+ RecordingJna stub = new RecordingJna();
+ injectStub(api, stub);
+
+ long zeroResult = api.mmap(0L, 64, 1, 2, 3, 4);
+ assertEquals(RecordingJna.ZERO_RESULT, zeroResult);
+ assertEquals(Pointer.NULL, stub.pointers.get(0));
+
+ long nonZeroResult = api.mmap(64L, 32, 5, 6, 7, 8);
+ assertEquals(RecordingJna.NON_ZERO_RESULT, nonZeroResult);
+ assertEquals(64L, Pointer.nativeValue(stub.pointers.get(1)));
+ }
+
+ private static void injectStub(JNAPosixAPI api, JNAPosixInterface stub) throws Exception {
+ Field field = JNAPosixAPI.class.getDeclaredField("jna");
+ if (!field.canAccess(api)) {
+ field.setAccessible(true);
+ }
+ long offset = UNSAFE.objectFieldOffset(field);
+ UNSAFE.putObject(api, offset, stub);
+ }
+
+ private static final class RecordingJna extends JNAPosixInterface {
+ static final long ZERO_RESULT = 111;
+ static final long NON_ZERO_RESULT = 222;
+
+ final ArrayList pointers = new ArrayList<>();
+
+ @Override
+ public long mmap(Pointer addr, long length, int prot, int flags, int fd, long offset) {
+ pointers.add(addr);
+ return Pointer.nativeValue(addr) == 0 ? ZERO_RESULT : NON_ZERO_RESULT;
+ }
+ }
+
+ /**
+ * Thin stub so we do not have to exercise the full native surface while still
+ * initialising {@link JNAPosixAPI}.
+ */
+ private static final class DummyJNAPosixAPI extends JNAPosixAPI {
+ private UnsupportedOperationException unsupported() {
+ return new UnsupportedOperationException("Only mmap is exercised in this test");
+ }
+
+ @Override
+ public int open(CharSequence path, int flags, int perm) {
+ throw unsupported();
+ }
+
+ @Override
+ public long lseek(int fd, long offset, int whence) {
+ throw unsupported();
+ }
+
+ @Override
+ public int ftruncate(int fd, long offset) {
+ throw unsupported();
+ }
+
+ @Override
+ public int lockf(int fd, int cmd, long len) {
+ throw unsupported();
+ }
+
+ @Override
+ public int close(int fd) {
+ throw unsupported();
+ }
+
+ @Override
+ public int fallocate(int fd, int mode, long offset, long length) {
+ throw unsupported();
+ }
+
+ @Override
+ public int madvise(long addr, long length, int advice) {
+ throw unsupported();
+ }
+
+ @Override
+ public int msync(long address, long length, int mode) {
+ throw unsupported();
+ }
+
+ @Override
+ public int munmap(long addr, long length) {
+ throw unsupported();
+ }
+
+ @Override
+ public long read(int fd, long dst, long len) {
+ throw unsupported();
+ }
+
+ @Override
+ public long write(int fd, long src, long len) {
+ throw unsupported();
+ }
+
+ @Override
+ public int gettimeofday(long timeval) {
+ throw unsupported();
+ }
+
+ @Override
+ public int sched_setaffinity(int pid, int cpusetsize, long mask) {
+ throw unsupported();
+ }
+
+ @Override
+ public int sched_getaffinity(int pid, int cpusetsize, long mask) {
+ throw unsupported();
+ }
+
+ @Override
+ public int lastError() {
+ throw unsupported();
+ }
+
+ @Override
+ public long clock_gettime(int clockId) throws IllegalArgumentException {
+ throw unsupported();
+ }
+
+ @Override
+ public long malloc(long size) {
+ throw unsupported();
+ }
+
+ @Override
+ public void free(long ptr) {
+ throw unsupported();
+ }
+
+ @Override
+ public int get_nprocs() {
+ throw unsupported();
+ }
+
+ @Override
+ public int get_nprocs_conf() {
+ throw unsupported();
+ }
+
+ @Override
+ public int getpid() {
+ throw unsupported();
+ }
+
+ @Override
+ public int gettid() {
+ throw unsupported();
+ }
+
+ @Override
+ public String strerror(int errno) {
+ throw unsupported();
+ }
+ }
+}
diff --git a/src/test/java/net/openhft/posix/internal/noop/NoOpPosixAPITest.java b/src/test/java/net/openhft/posix/internal/noop/NoOpPosixAPITest.java
new file mode 100644
index 0000000..a95ff32
--- /dev/null
+++ b/src/test/java/net/openhft/posix/internal/noop/NoOpPosixAPITest.java
@@ -0,0 +1,43 @@
+package net.openhft.posix.internal.noop;
+
+import net.openhft.posix.MAdviseFlag;
+import net.openhft.posix.MSyncFlag;
+import net.openhft.posix.OpenFlag;
+import net.openhft.posix.PosixRuntimeException;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Behavioural checks for {@link NoOpPosixAPI}.
+ * Verifies which calls return graceful no-ops and which throw so the safety net
+ * stays consistent with POSIX-FN-010.
+ */
+public class NoOpPosixAPITest {
+
+ private final NoOpPosixAPI api = new NoOpPosixAPI("test");
+
+ @Test
+ public void noopOperationsReturnSuccessCodes() {
+ assertEquals(0, api.fallocate(1, 0, 0, 1));
+ assertEquals(0, api.ftruncate(1, 128));
+ assertEquals(0, api.madvise(0, 16, MAdviseFlag.MADV_NORMAL.value()));
+ assertEquals(0, api.msync(0, 16, MSyncFlag.MS_ASYNC.value()));
+ assertEquals(0, api.sched_setaffinity(1234, 8, 0L));
+ assertEquals(-1, api.sched_getaffinity(1234, 8, 0L));
+ assertEquals(0, api.lastError());
+ assertNull(api.strerror(42));
+ }
+
+ @Test
+ public void exceptionalOperationsThrow() {
+ assertThrows(PosixRuntimeException.class, () -> api.open("path", OpenFlag.O_RDONLY.value(), 0644));
+ assertThrows(PosixRuntimeException.class, () -> api.read(3, 0L, 16));
+ assertThrows(PosixRuntimeException.class, () -> api.write(3, 0L, 16));
+ assertThrows(PosixRuntimeException.class, () -> api.gettimeofday(0));
+ assertThrows(PosixRuntimeException.class, () -> api.clock_gettime(0));
+ assertThrows(PosixRuntimeException.class, () -> api.malloc(4));
+ assertThrows(PosixRuntimeException.class, () -> api.getpid());
+ assertThrows(PosixRuntimeException.class, () -> api.gettid());
+ }
+}
diff --git a/src/test/java/net/openhft/posix/internal/raw/RawPosixAPITest.java b/src/test/java/net/openhft/posix/internal/raw/RawPosixAPITest.java
new file mode 100644
index 0000000..466fd28
--- /dev/null
+++ b/src/test/java/net/openhft/posix/internal/raw/RawPosixAPITest.java
@@ -0,0 +1,148 @@
+package net.openhft.posix.internal.raw;
+
+import net.openhft.posix.MclFlag;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Ensures {@link RawPosixAPI} can be subclassed without extra wiring and that
+ * default memory-lock helpers retain their documented no-op behaviour.
+ */
+public class RawPosixAPITest {
+
+ private final DummyRaw api = new DummyRaw();
+
+ @Test
+ public void defaultMemoryLockHelpersReturnFalseOrNoOp() {
+ assertFalse(api.mlock(0L, 128));
+ assertFalse(api.mlock2(0L, 128, true));
+ api.mlockall(MclFlag.MclCurrent); // should not throw
+ }
+
+ private static final class DummyRaw extends RawPosixAPI {
+ private UnsupportedOperationException unsupported() {
+ return new UnsupportedOperationException("Raw stub only exercises defaults");
+ }
+
+ @Override
+ public int open(CharSequence path, int flags, int perm) {
+ throw unsupported();
+ }
+
+ @Override
+ public long lseek(int fd, long offset, int whence) {
+ throw unsupported();
+ }
+
+ @Override
+ public int ftruncate(int fd, long offset) {
+ throw unsupported();
+ }
+
+ @Override
+ public int lockf(int fd, int cmd, long len) {
+ throw unsupported();
+ }
+
+ @Override
+ public int close(int fd) {
+ throw unsupported();
+ }
+
+ @Override
+ public int fallocate(int fd, int mode, long offset, long length) {
+ throw unsupported();
+ }
+
+ @Override
+ public int madvise(long addr, long length, int advice) {
+ throw unsupported();
+ }
+
+ @Override
+ public int msync(long address, long length, int mode) {
+ throw unsupported();
+ }
+
+ @Override
+ public long mmap(long addr, long length, int prot, int flags, int fd, long offset) {
+ throw unsupported();
+ }
+
+ @Override
+ public int munmap(long addr, long length) {
+ throw unsupported();
+ }
+
+ @Override
+ public long read(int fd, long dst, long len) {
+ throw unsupported();
+ }
+
+ @Override
+ public long write(int fd, long src, long len) {
+ throw unsupported();
+ }
+
+ @Override
+ public int gettimeofday(long timeval) {
+ throw unsupported();
+ }
+
+ @Override
+ public int sched_setaffinity(int pid, int cpusetsize, long mask) {
+ throw unsupported();
+ }
+
+ @Override
+ public int sched_getaffinity(int pid, int cpusetsize, long mask) {
+ throw unsupported();
+ }
+
+ @Override
+ public int lastError() {
+ throw unsupported();
+ }
+
+ @Override
+ public long clock_gettime(int clockId) {
+ throw unsupported();
+ }
+
+ @Override
+ public long malloc(long size) {
+ throw unsupported();
+ }
+
+ @Override
+ public void free(long ptr) {
+ throw unsupported();
+ }
+
+ @Override
+ public int get_nprocs() {
+ throw unsupported();
+ }
+
+ @Override
+ public int get_nprocs_conf() {
+ throw unsupported();
+ }
+
+ @Override
+ public int getpid() {
+ throw unsupported();
+ }
+
+ @Override
+ public int gettid() {
+ throw unsupported();
+ }
+
+ @Override
+ public String strerror(int errno) {
+ throw unsupported();
+ }
+ }
+}
From 9be13ce3dbaa4e4b43ea33b8bec937a802146694 Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Sun, 26 Oct 2025 04:17:51 +0000
Subject: [PATCH 02/17] Enable AsciiDoc section numbering
---
README.adoc | 2 +-
src/main/adoc/decision-log.adoc | 1 +
src/main/adoc/project-requirements.adoc | 2 +-
3 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/README.adoc b/README.adoc
index f1d606c..40f2a5a 100644
--- a/README.adoc
+++ b/README.adoc
@@ -1,6 +1,7 @@
= Posix
Peter Lawrey, 31/08/2021
:toc:
+:sectnums:
:icons: font
:encoding: ISO-8859-1
:lang: en-GB
@@ -123,4 +124,3 @@ Add JVM arg:
* link:src/main/adoc/project-requirements.adoc[Functional requirements]
* link:src/main/adoc/decision-log.adoc[Architecture decision log]
* link:https://man7.org/linux/man-pages/[Linux man-pages]
-
diff --git a/src/main/adoc/decision-log.adoc b/src/main/adoc/decision-log.adoc
index 812f121..a0b9600 100644
--- a/src/main/adoc/decision-log.adoc
+++ b/src/main/adoc/decision-log.adoc
@@ -1,6 +1,7 @@
= Decision Log – OpenHFT Posix
:doctype: book
:toc:
+:sectnums:
:icons: font
:lang: en-GB
:encoding: ISO-8859-1
diff --git a/src/main/adoc/project-requirements.adoc b/src/main/adoc/project-requirements.adoc
index 165d4d9..2c492b6 100644
--- a/src/main/adoc/project-requirements.adoc
+++ b/src/main/adoc/project-requirements.adoc
@@ -1,6 +1,7 @@
= Functional Requirements - OpenHFT Posix
:doctype: book
:toc:
+:sectnums:
:encoding: ISO-8859-1
:lang: en-GB
@@ -84,4 +85,3 @@ pure functions, safe in static initialisers.
printing a latency histogram; exit 0 when all checks pass.
|===
-
From b8ae157dfea806f21d10ef615a6081a317c74261 Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Sun, 26 Oct 2025 04:30:53 +0000
Subject: [PATCH 03/17] Fix affinity mask bounds and guard tests
---
src/main/java/net/openhft/posix/PosixAPI.java | 25 ++++-
.../posix/internal/jna/JNAPosixAPI.java | 2 +-
.../posix/PosixAffinityHelperTest.java | 104 ++++++++++++++++++
3 files changed, 124 insertions(+), 7 deletions(-)
create mode 100644 src/test/java/net/openhft/posix/PosixAffinityHelperTest.java
diff --git a/src/main/java/net/openhft/posix/PosixAPI.java b/src/main/java/net/openhft/posix/PosixAPI.java
index 563c457..40e6d79 100644
--- a/src/main/java/net/openhft/posix/PosixAPI.java
+++ b/src/main/java/net/openhft/posix/PosixAPI.java
@@ -362,9 +362,7 @@ default String sched_getaffinity_summary(int pid) {
* @return 0 on success, -1 on error.
*/
default int sched_setaffinity_as(int pid, int cpu) {
- final int nprocs_conf = get_nprocs_conf();
- final int size = Math.max(Long.BYTES,
- (int) ((((long) nprocs_conf + (Long.SIZE - 1)) / Long.SIZE) * Long.BYTES));
+ final int size = requiredMaskBytes(Math.max(cpu, Math.max(0, get_nprocs_conf() - 1)));
long ptr = malloc(size);
try {
for (int i = 0; i < size; i += 4)
@@ -387,9 +385,9 @@ default int sched_setaffinity_as(int pid, int cpu) {
* @return 0 on success, -1 on error.
*/
default int sched_setaffinity_range(int pid, int from, int to) {
- final int nprocs_conf = get_nprocs_conf();
- final int size = Math.max(Long.BYTES,
- (int) ((((long) nprocs_conf + (Long.SIZE - 1)) / Long.SIZE) * Long.BYTES));
+ if (to < from)
+ throw new IllegalArgumentException("from (" + from + ") must be <= to (" + to + ')');
+ final int size = requiredMaskBytes(Math.max(to, Math.max(0, get_nprocs_conf() - 1)));
long ptr = malloc(size);
try {
for (int i = 0; i < size; i += 4)
@@ -407,6 +405,21 @@ default int sched_setaffinity_range(int pid, int from, int to) {
}
}
+ /**
+ * Calculates the number of bytes required to store a CPU mask that includes the supplied index.
+ *
+ * @param highestCpuInclusive highest CPU index we need to represent
+ * @return number of bytes rounded up to the nearest multiple of {@link Long#BYTES}
+ */
+ static int requiredMaskBytes(int highestCpuInclusive) {
+ int cpus = Math.max(0, highestCpuInclusive) + 1;
+ long words = ((long) cpus + (Long.SIZE - 1)) / Long.SIZE;
+ long bytes = Math.max(1L, words) * Long.BYTES;
+ if (bytes > Integer.MAX_VALUE)
+ throw new IllegalArgumentException("CPU mask exceeds supported size: " + bytes + " bytes");
+ return (int) bytes;
+ }
+
/**
* Returns the current wall clock time in microseconds.
* Note that clock_gettime() is more accurate if available.
diff --git a/src/main/java/net/openhft/posix/internal/jna/JNAPosixAPI.java b/src/main/java/net/openhft/posix/internal/jna/JNAPosixAPI.java
index 6afd9cd..a9409f9 100644
--- a/src/main/java/net/openhft/posix/internal/jna/JNAPosixAPI.java
+++ b/src/main/java/net/openhft/posix/internal/jna/JNAPosixAPI.java
@@ -16,7 +16,7 @@
* the JVM.
*/
public abstract class JNAPosixAPI implements PosixAPI {
- private static final Pointer NULL = Pointer.createConstant(0);
+ private static final Pointer NULL = Pointer.NULL;
// JNA interface for POSIX functions
private final JNAPosixInterface jna = new JNAPosixInterface();
diff --git a/src/test/java/net/openhft/posix/PosixAffinityHelperTest.java b/src/test/java/net/openhft/posix/PosixAffinityHelperTest.java
new file mode 100644
index 0000000..aa779a7
--- /dev/null
+++ b/src/test/java/net/openhft/posix/PosixAffinityHelperTest.java
@@ -0,0 +1,104 @@
+package net.openhft.posix;
+
+import net.openhft.posix.internal.UnsafeMemory;
+import net.openhft.posix.internal.noop.NoOpPosixAPI;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+public class PosixAffinityHelperTest {
+
+ private static final long GUARD = 0x7A5A5A5A7A5A5A5AL;
+ private static final int BYTE_BASE = UnsafeMemory.UNSAFE.arrayBaseOffset(byte[].class);
+
+ @Test
+ public void schedSetAffinityRangeAllocatesSpaceForUpperBound() {
+ GuardedPosixAPI posix = new GuardedPosixAPI(64);
+
+ posix.sched_setaffinity_range(123, 64, 64);
+
+ assertEquals("expected 2 words (16 bytes) to cover CPU 64",
+ Long.BYTES * 2, posix.lastCpusetsize);
+ int thirdWord = posix.wordAsInt(2);
+ assertEquals("bit for CPU 64 should be set", 1, thirdWord & 1);
+ }
+
+ @Test
+ public void schedSetAffinityRangeRejectsDescendingBounds() {
+ GuardedPosixAPI posix = new GuardedPosixAPI(4);
+ assertThrows(IllegalArgumentException.class,
+ () -> posix.sched_setaffinity_range(1, 5, 4));
+ }
+
+ private static final class GuardedPosixAPI extends NoOpPosixAPI {
+ private final int nprocs;
+ private final Map allocations = new HashMap<>();
+ private int lastCpusetsize;
+ private byte[] lastMaskBytes;
+
+ GuardedPosixAPI(int nprocs) {
+ super("guarded");
+ this.nprocs = nprocs;
+ }
+
+ @Override
+ public int sched_setaffinity(int pid, int cpusetsize, long mask) {
+ Allocation allocation = allocations.get(mask);
+ if (allocation == null)
+ throw new IllegalStateException("Unknown allocation for mask pointer " + mask);
+ if (cpusetsize < allocation.requestedSize)
+ throw new AssertionError("cpusetsize " + cpusetsize + " < allocated " + allocation.requestedSize);
+ long guard = UnsafeMemory.UNSAFE.getLong(mask + allocation.requestedSize);
+ if (guard != GUARD)
+ throw new AssertionError("Guard corrupted for mask pointer " + mask);
+ lastCpusetsize = cpusetsize;
+ byte[] snapshot = new byte[(int) allocation.requestedSize];
+ UnsafeMemory.UNSAFE.copyMemory(null, mask, snapshot, BYTE_BASE, allocation.requestedSize);
+ lastMaskBytes = snapshot;
+ return 0;
+ }
+
+ @Override
+ public long malloc(long size) {
+ long actual = size + Long.BYTES;
+ long ptr = UnsafeMemory.UNSAFE.allocateMemory(actual);
+ UnsafeMemory.UNSAFE.setMemory(ptr, actual, (byte) 0);
+ UnsafeMemory.UNSAFE.putLong(ptr + size, GUARD);
+ allocations.put(ptr, new Allocation(size, actual));
+ return ptr;
+ }
+
+ @Override
+ public void free(long ptr) {
+ Allocation allocation = allocations.remove(ptr);
+ if (allocation != null)
+ UnsafeMemory.UNSAFE.freeMemory(ptr);
+ }
+
+ @Override
+ public int get_nprocs_conf() {
+ return nprocs;
+ }
+
+ int wordAsInt(int wordIndex) {
+ int offset = wordIndex * Integer.BYTES;
+ if (lastMaskBytes == null || lastMaskBytes.length < offset + Integer.BYTES)
+ return 0;
+ return UnsafeMemory.UNSAFE.getInt(lastMaskBytes, (long) BYTE_BASE + offset);
+ }
+ }
+
+ private static final class Allocation {
+ final long requestedSize;
+ final long actualSize;
+
+ Allocation(long requestedSize, long actualSize) {
+ this.requestedSize = requestedSize;
+ this.actualSize = actualSize;
+ }
+ }
+}
From 8e75a1edafb583b39a47cce17fd0e1b25d6120ca Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Mon, 27 Oct 2025 07:06:44 +0000
Subject: [PATCH 04/17] Add quality Maven profile and encoding fixes
---
pom.xml | 1195 +++++++++++++++--
src/main/java/net/openhft/posix/PosixAPI.java | 19 +-
src/main/java/net/openhft/posix/ProcMaps.java | 6 +-
src/main/resources/spotbugs-exclude.xml | 17 +
4 files changed, 1120 insertions(+), 117 deletions(-)
create mode 100644 src/main/resources/spotbugs-exclude.xml
diff --git a/pom.xml b/pom.xml
index aba1a48..a17d222 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,123 +1,1100 @@
- 4.0.0
+
+
+
+
+
+
+ 4.0.0
+
+
+
-
- net.openhft
- java-parent-pom
- 1.27ea1
-
-
-
- posix
- 2.27ea3-SNAPSHOT
- OpenHFT/Posix
- OpenHFT Java Posix APIs
- jar
+
+
+
+
+
+
+
+
+
+
+ net.openhft
+
+
+
+
+
+
+ java-parent-pom
+
+
+
+
+
+
+ 1.27ea1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
- net.openhft
- third-party-bom
- 3.27ea2
- pom
- import
-
-
-
+
+
+
+ posix
+
+
+
+
+
+
+ 2.27ea3-SNAPSHOT
+
+
+
+
+
+
+ OpenHFT/Posix
+
+
+
+
+
+
+ OpenHFT Java Posix APIs
+
+
+
+
+
+
+ jar
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- org.slf4j
- slf4j-api
-
-
- net.java.dev.jna
- jna
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ net.openhft
+
+
+
+
+
+
+ third-party-bom
+
+
+
+
+
+
+ 3.27ea2
+
+
+
+
+
+
+ pom
+
+
+
+
+
+
+ import
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- net.java.dev.jna
- jna-platform
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.slf4j
+
+
+
+
+
+
+ slf4j-api
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ net.java.dev.jna
+
+
+
+
+
+
+ jna
+
+
+
+
+
+
+
+
+
+
-
- com.github.jnr
- jnr-ffi
-
-
- com.github.jnr
- jnr-constants
-
+
+
+
+
+
+
+
+
+
+
+ net.java.dev.jna
+
+
+
+
+
+
+ jna-platform
+
+
+
+
+
+
+
+
+
+
-
-
- junit
- junit
- test
-
+
+
+
+
+
+
+
+
+
+
+ com.github.jnr
+
+
+
+
+
+
+ jnr-ffi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ com.github.jnr
+
+
+
+
+
+
+ jnr-constants
+
+
+
+
+
+
+
+
+
+
-
- org.slf4j
- slf4j-simple
- test
-
-
+
+
+
+
+
+
+
+
+
+
+
+ junit
+
+
+
+
+
+
+ junit
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.slf4j
+
+
+
+
+
+
+ slf4j-simple
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+
+
+
+
+
+
+ maven-source-plugin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ attach-sources
+
+
+
+
+
+
+
+
+
+
+
+
+
+ jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ java11
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [11,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- org.apache.maven.plugins
- maven-source-plugin
-
-
- attach-sources
-
- jar
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
- false
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+
+
+
+
+
+
+ maven-resources-plugin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ src/main/resources/META-INF/services
+
+
+
+
+
+
+
+
+
+
+
+
+
+ *
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
- java11
-
- [11,
-
-
-
-
- org.apache.maven.plugins
- maven-resources-plugin
-
-
-
- src/main/resources/META-INF/services
-
- *
-
-
-
-
-
-
-
-
-
-
- scm:git:git@github.com:OpenHFT/posix.git
- scm:git:git@github.com:OpenHFT/posix.git
- scm:git:git@github.com:OpenHFT/posix.git
- ea
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ quality
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ com.github.spotbugs
+
+
+
+
+ spotbugs-maven-plugin
+
+
+
+
+ 4.8.6.4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ spotbugs
+
+
+
+
+
+
+
+
+
+ check
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ max
+
+
+
+
+ Low
+
+
+
+
+ src/main/resources/spotbugs-exclude.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+
+
+
+
+ maven-pmd-plugin
+
+
+
+
+ 3.21.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pmd
+
+
+
+
+
+
+
+
+
+ check
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.jacoco
+
+
+
+
+ jacoco-maven-plugin
+
+
+
+
+ 0.8.11
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ prepare-agent
+
+
+
+
+
+
+
+
+
+ prepare-agent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ report
+
+
+
+
+
+
+
+
+
+ report
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ scm:git:git@github.com:OpenHFT/posix.git
+
+
+
+
+
+
+ scm:git:git@github.com:OpenHFT/posix.git
+
+
+
+
+
+
+ scm:git:git@github.com:OpenHFT/posix.git
+
+
+
+
+
+
+ ea
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/net/openhft/posix/PosixAPI.java b/src/main/java/net/openhft/posix/PosixAPI.java
index 40e6d79..392840e 100644
--- a/src/main/java/net/openhft/posix/PosixAPI.java
+++ b/src/main/java/net/openhft/posix/PosixAPI.java
@@ -6,6 +6,7 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import static net.openhft.posix.internal.UnsafeMemory.UNSAFE;
@@ -264,9 +265,17 @@ default long du(String filename) throws IOException {
ProcessBuilder pb = new ProcessBuilder("du", filename);
pb.redirectErrorStream(true);
final Process process = pb.start();
- try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
- String line = br.readLine();
- return Long.parseUnsignedLong(line.split("\\s+")[0]);
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(
+ process.getInputStream(), StandardCharsets.UTF_8))) {
+ final String line = br.readLine();
+ if (line == null) {
+ throw new IOException("du produced no output for " + filename);
+ }
+ final String[] tokens = line.split("\\s+");
+ if (tokens.length == 0) {
+ throw new IOException("du output malformed for " + filename + ": \"" + line + "\"");
+ }
+ return Long.parseUnsignedLong(tokens[0]);
}
}
@@ -321,9 +330,7 @@ default String sched_getaffinity_summary(int pid) {
final int mask = 1 << (i & 31);
final int word = UNSAFE.getInt(wordAddr);
if ((word & mask) != 0) {
- if (set) {
- // nothing.
- } else {
+ if (!set) {
start = i;
set = true;
}
diff --git a/src/main/java/net/openhft/posix/ProcMaps.java b/src/main/java/net/openhft/posix/ProcMaps.java
index fd51e96..ceb852b 100644
--- a/src/main/java/net/openhft/posix/ProcMaps.java
+++ b/src/main/java/net/openhft/posix/ProcMaps.java
@@ -1,7 +1,9 @@
package net.openhft.posix;
import java.io.BufferedReader;
-import java.io.FileReader;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -30,7 +32,7 @@ public final class ProcMaps {
* @throws IOException on read failure
*/
private ProcMaps(Object proc) throws IOException {
- try (BufferedReader br = new BufferedReader(new FileReader("/proc/" + proc + "/maps"))) {
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/" + proc + "/maps"), StandardCharsets.UTF_8))) {
for (String line; (line = br.readLine()) != null; ) {
mappingList.add(new Mapping(line));
}
diff --git a/src/main/resources/spotbugs-exclude.xml b/src/main/resources/spotbugs-exclude.xml
new file mode 100644
index 0000000..974738d
--- /dev/null
+++ b/src/main/resources/spotbugs-exclude.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 1b5d27f89def1d3ec566da2a7e49c8a871c6518a Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Mon, 27 Oct 2025 13:08:54 +0000
Subject: [PATCH 05/17] Adjust code-review profile tooling
---
pom.xml | 1361 ++++-------------
src/main/config/pmd-exclude.properties | 5 +
src/main/config/spotbugs-exclude.xml | 38 +
src/main/java/net/openhft/posix/PosixAPI.java | 7 +-
src/main/java/net/openhft/posix/ProcMaps.java | 3 +
.../posix/internal/jnr/JNRPosixAPI.java | 11 +-
.../posix/internal/jnr/WinJNRPosixAPI.java | 12 +-
src/main/resources/spotbugs-exclude.xml | 17 -
8 files changed, 342 insertions(+), 1112 deletions(-)
create mode 100644 src/main/config/pmd-exclude.properties
create mode 100644 src/main/config/spotbugs-exclude.xml
delete mode 100644 src/main/resources/spotbugs-exclude.xml
diff --git a/pom.xml b/pom.xml
index a17d222..76070fe 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,1100 +1,289 @@
-
-
-
-
-
-
-
-
- 4.0.0
-
-
-
+
+
+ 4.0.0
-
-
-
-
-
-
-
-
-
-
- net.openhft
-
-
-
-
-
-
- java-parent-pom
-
-
-
-
-
-
- 1.27ea1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ net.openhft
+ java-parent-pom
+ 1.27ea1
+
+
+
+ posix
+ 2.27ea3-SNAPSHOT
+ OpenHFT/Posix
+ OpenHFT Java Posix APIs
+ jar
+
+
+ 3.6.0
+ 10.26.1
+ 4.9.8.1
+ 1.14.0
+ 3.28.0
+ 0.8.14
+ 0.80
+ 0.70
+ 1.23ea6
+
-
-
-
- posix
-
-
-
-
-
-
- 2.27ea3-SNAPSHOT
-
-
-
-
-
-
- OpenHFT/Posix
-
-
-
-
-
-
- OpenHFT Java Posix APIs
-
-
-
-
-
-
- jar
-
-
-
+
+
+
+ net.openhft
+ third-party-bom
+ 3.27ea2
+ pom
+ import
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- net.openhft
-
-
-
-
-
-
- third-party-bom
-
-
-
-
-
-
- 3.27ea2
-
-
-
-
-
-
- pom
-
-
-
-
-
-
- import
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ org.slf4j
+ slf4j-api
+
+
+ com.github.spotbugs
+ spotbugs-annotations
+ 4.9.0
+ provided
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- org.slf4j
-
-
-
-
-
-
- slf4j-api
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- net.java.dev.jna
-
-
-
-
-
-
- jna
-
-
-
-
-
-
-
-
-
-
+
+ net.java.dev.jna
+ jna
+
-
-
-
-
-
-
-
-
-
-
- net.java.dev.jna
-
-
-
-
-
-
- jna-platform
-
-
-
-
-
-
-
-
-
-
+
+ net.java.dev.jna
+ jna-platform
+
-
-
-
-
-
-
-
-
-
-
- com.github.jnr
-
-
-
-
-
-
- jnr-ffi
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- com.github.jnr
-
-
-
-
-
-
- jnr-constants
-
-
-
-
-
-
-
-
-
-
+
+ com.github.jnr
+ jnr-ffi
+
+
+ com.github.jnr
+ jnr-constants
+
-
-
-
-
-
-
-
-
-
-
-
- junit
-
-
-
-
-
-
- junit
-
-
-
-
-
-
- test
-
-
-
-
-
-
-
-
-
-
+
+
+ junit
+ junit
+ test
+
-
-
-
-
-
-
-
-
-
-
- org.slf4j
-
-
-
-
-
-
- slf4j-simple
-
-
-
-
-
-
- test
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ org.slf4j
+ slf4j-simple
+ test
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
-
-
-
-
-
-
- maven-source-plugin
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- attach-sources
-
-
-
-
-
-
-
-
-
-
-
-
-
- jar
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
-
-
-
-
-
-
- maven-compiler-plugin
-
-
-
-
-
-
-
-
-
-
-
-
-
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- java11
-
-
-
-
-
-
-
-
-
-
-
-
-
- [11,
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
-
-
-
-
-
-
- maven-resources-plugin
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- src/main/resources/META-INF/services
-
-
-
-
-
-
-
-
-
-
-
-
-
- *
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- quality
-
-
-
-
-
-
-
-
-
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
- com.github.spotbugs
-
-
-
-
- spotbugs-maven-plugin
-
-
-
-
- 4.8.6.4
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- spotbugs
-
-
-
-
-
-
-
-
-
- check
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- max
-
-
-
-
- Low
-
-
-
-
- src/main/resources/spotbugs-exclude.xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
-
-
-
-
- maven-pmd-plugin
-
-
-
-
- 3.21.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- pmd
-
-
-
-
-
-
-
-
-
- check
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- org.jacoco
-
-
-
-
- jacoco-maven-plugin
-
-
-
-
- 0.8.11
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- prepare-agent
-
-
-
-
-
-
-
-
-
- prepare-agent
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- report
-
-
-
-
-
-
-
-
-
- report
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ false
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- scm:git:git@github.com:OpenHFT/posix.git
-
-
-
-
-
-
- scm:git:git@github.com:OpenHFT/posix.git
-
-
-
-
-
-
- scm:git:git@github.com:OpenHFT/posix.git
-
-
-
-
-
-
- ea
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ java11
+
+ [11,
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+
+ src/main/resources/META-INF/services
+
+ *
+
+
+
+
+
+
+
+
+
+ code-review
+
+ false
+
+
+ 0.0
+ 0.0
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${checkstyle.version}
+
+
+ com.puppycrawl.tools
+ checkstyle
+ ${puppycrawl.version}
+
+
+ net.openhft
+ chronicle-quality-rules
+ ${chronicle-quality-rules.version}
+
+
+
+
+ checkstyle
+ verify
+
+ check
+
+
+
+
+ net/openhft/quality/checkstyle/checkstyle.xml
+ true
+ true
+ warning
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ ${spotbugs.version}
+
+
+ com.h3xstream.findsecbugs
+ findsecbugs-plugin
+ ${findsecbugs.version}
+
+
+
+
+ spotbugs
+ verify
+
+ check
+
+
+
+
+ Max
+ Low
+ true
+ ${project.basedir}/src/main/config/spotbugs-exclude.xml
+
+
+ com.h3xstream.findsecbugs
+ findsecbugs-plugin
+ ${findsecbugs.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-pmd-plugin
+ ${maven-pmd-plugin.version}
+
+
+ pmd
+ verify
+
+ check
+
+
+
+
+ true
+ true
+ src/main/config/pmd-exclude.properties
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco-maven-plugin.version}
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+ report
+ verify
+
+ report
+
+
+
+ check
+ verify
+
+ check
+
+
+
+
+ BUNDLE
+
+
+ LINE
+ COVEREDRATIO
+ ${jacoco.line.coverage}
+
+
+ BRANCH
+ COVEREDRATIO
+ ${jacoco.branch.coverage}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ scm:git:git@github.com:OpenHFT/posix.git
+ scm:git:git@github.com:OpenHFT/posix.git
+ scm:git:git@github.com:OpenHFT/posix.git
+ ea
+
diff --git a/src/main/config/pmd-exclude.properties b/src/main/config/pmd-exclude.properties
new file mode 100644
index 0000000..c1eeca3
--- /dev/null
+++ b/src/main/config/pmd-exclude.properties
@@ -0,0 +1,5 @@
+# PMD exclusions with justifications
+# Format: filepath=rule1,rule2
+#
+# Example:
+# net/openhft/posix/LegacyParser.java=AvoidReassigningParameters,TooManyFields
diff --git a/src/main/config/spotbugs-exclude.xml b/src/main/config/spotbugs-exclude.xml
new file mode 100644
index 0000000..0e97348
--- /dev/null
+++ b/src/main/config/spotbugs-exclude.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POSIX-SEC-204: ProcessBuilder is invoked with fixed argv and never shells out; arguments are passed as execve tokens so there is no injection surface.
+
+
+
+
+
+
+ POSIX-OPS-102: Reads the kernel-managed /proc/<pid>/maps file for diagnostics; caller supplies PID but resource remains confined to procfs.
+
+
+
+
+
+
+ POSIX-API-117: Methods wrap errno reporting, throwing RuntimeException to preserve existing API contracts; change to checked exceptions would be breaking.
+
+
+
diff --git a/src/main/java/net/openhft/posix/PosixAPI.java b/src/main/java/net/openhft/posix/PosixAPI.java
index 392840e..1a31be4 100644
--- a/src/main/java/net/openhft/posix/PosixAPI.java
+++ b/src/main/java/net/openhft/posix/PosixAPI.java
@@ -1,5 +1,6 @@
package net.openhft.posix;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.openhft.posix.internal.PosixAPIHolder;
import net.openhft.posix.internal.UnsafeMemory;
@@ -261,6 +262,7 @@ default int open(CharSequence path, OpenFlag flags, int perm) {
* @return The disk usage in bytes.
* @throws IOException If an I/O error occurs.
*/
+ @SuppressFBWarnings(value = "COMMAND_INJECTION", justification = "POSIX-SEC-204: ProcessBuilder uses fixed argv without shell expansion")
default long du(String filename) throws IOException {
ProcessBuilder pb = new ProcessBuilder("du", filename);
pb.redirectErrorStream(true);
@@ -315,8 +317,7 @@ default long du(String filename) throws IOException {
*/
default String sched_getaffinity_summary(int pid) {
final int nprocs_conf = get_nprocs_conf();
- final int size = Math.max(Long.BYTES,
- (int) ((((long) nprocs_conf + (Long.SIZE - 1)) / Long.SIZE) * Long.BYTES));
+ final int size = Math.max(Long.BYTES, requiredMaskBytes(nprocs_conf - 1));
long ptr = malloc(size);
boolean set = false;
int start = 0;
@@ -420,7 +421,7 @@ default int sched_setaffinity_range(int pid, int from, int to) {
*/
static int requiredMaskBytes(int highestCpuInclusive) {
int cpus = Math.max(0, highestCpuInclusive) + 1;
- long words = ((long) cpus + (Long.SIZE - 1)) / Long.SIZE;
+ long words = ((long) cpus + Long.SIZE - 1) / Long.SIZE;
long bytes = Math.max(1L, words) * Long.BYTES;
if (bytes > Integer.MAX_VALUE)
throw new IllegalArgumentException("CPU mask exceeds supported size: " + bytes + " bytes");
diff --git a/src/main/java/net/openhft/posix/ProcMaps.java b/src/main/java/net/openhft/posix/ProcMaps.java
index ceb852b..15b16f9 100644
--- a/src/main/java/net/openhft/posix/ProcMaps.java
+++ b/src/main/java/net/openhft/posix/ProcMaps.java
@@ -1,5 +1,7 @@
package net.openhft.posix;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
@@ -21,6 +23,7 @@
*
* @see proc(5)
*/
+@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "POSIX-OPS-102: reads kernel-managed /proc//maps entries only")
public final class ProcMaps {
// A list to hold the memory mappings
private final List mappingList = new ArrayList<>();
diff --git a/src/main/java/net/openhft/posix/internal/jnr/JNRPosixAPI.java b/src/main/java/net/openhft/posix/internal/jnr/JNRPosixAPI.java
index b9f0109..77c4a95 100644
--- a/src/main/java/net/openhft/posix/internal/jnr/JNRPosixAPI.java
+++ b/src/main/java/net/openhft/posix/internal/jnr/JNRPosixAPI.java
@@ -5,6 +5,7 @@
import jnr.ffi.Pointer;
import jnr.ffi.Runtime;
import jnr.ffi.provider.FFIProvider;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.openhft.posix.*;
import net.openhft.posix.internal.UnsafeMemory;
import net.openhft.posix.internal.core.Jvm;
@@ -24,13 +25,14 @@
* hard-coded numbers chosen for common architectures. If the kernel does not
* recognise a number the call gracefully falls back to the available wrapper.
*/
+@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_RUNTIMEEXCEPTION", justification = "POSIX-API-117: propagate errno via RuntimeException to honour existing interface")
public final class JNRPosixAPI implements PosixAPI {
private static final Logger LOGGER = LoggerFactory.getLogger(JNRPosixAPI.class);
// JNR Runtime and Platform instances
- static final jnr.ffi.Runtime RUNTIME = FFIProvider.getSystemProvider().getRuntime();
- static final jnr.ffi.Platform NATIVE_PLATFORM = Platform.getNativePlatform();
+ static final Runtime RUNTIME = FFIProvider.getSystemProvider().getRuntime();
+ static final Platform NATIVE_PLATFORM = Platform.getNativePlatform();
static final String STANDARD_C_LIBRARY_NAME = NATIVE_PLATFORM.getStandardCLibraryName();
static final Pointer NULL = Pointer.wrap(RUNTIME, 0);
@@ -261,6 +263,10 @@ public void close() throws IOException {
throw new IOException("Failed to release lock");
}
}
+
+ void ensureAcquired() {
+ // intentional no-op; documents that the lock is held for the try-with-resource scope
+ }
}
@Override
@@ -293,6 +299,7 @@ public int fallocate(int fd, int mode, long offset, long length) {
// NB: this use case uses cooperative locking to help close a small race window
if(mode == 0) {
try(FileLocker lock = new FileLocker(fd)) {
+ lock.ensureAcquired();
int ret = jnr.posix_fallocate(fd, offset, length);
if(ret == 0)
return ret;
diff --git a/src/main/java/net/openhft/posix/internal/jnr/WinJNRPosixAPI.java b/src/main/java/net/openhft/posix/internal/jnr/WinJNRPosixAPI.java
index 63737eb..a4a1a2d 100644
--- a/src/main/java/net/openhft/posix/internal/jnr/WinJNRPosixAPI.java
+++ b/src/main/java/net/openhft/posix/internal/jnr/WinJNRPosixAPI.java
@@ -1,6 +1,7 @@
package net.openhft.posix.internal.jnr;
import jnr.ffi.Platform;
+import jnr.ffi.Runtime;
import jnr.ffi.provider.FFIProvider;
import net.openhft.posix.PosixAPI;
@@ -25,7 +26,7 @@
public final class WinJNRPosixAPI implements PosixAPI {
// JNR Runtime and Platform instances
- static final jnr.ffi.Runtime RUNTIME = FFIProvider.getSystemProvider().getRuntime();
+ static final Runtime RUNTIME = FFIProvider.getSystemProvider().getRuntime();
static final Platform NATIVE_PLATFORM = Platform.getNativePlatform();
static final String STANDARD_C_LIBRARY_NAME = NATIVE_PLATFORM.getStandardCLibraryName();
@@ -120,7 +121,7 @@ public int get_nprocs() {
@Override
public int get_nprocs_conf() {
- return Runtime.getRuntime().availableProcessors();
+ return java.lang.Runtime.getRuntime().availableProcessors();
}
@Override
@@ -141,8 +142,11 @@ public int munmap(long addr, long length) {
@Override
public int gettimeofday(long timeval) {
long now = System.currentTimeMillis();
- UNSAFE.putLong(timeval, now / 1000);
- UNSAFE.putLong(timeval + 8, (now % 1000) * 1000);
+ long seconds = now / 1000;
+ long remainderMillis = now % 1000;
+ long micros = remainderMillis * 1000;
+ UNSAFE.putLong(timeval, seconds);
+ UNSAFE.putLong(timeval + 8, micros);
return 0;
}
diff --git a/src/main/resources/spotbugs-exclude.xml b/src/main/resources/spotbugs-exclude.xml
deleted file mode 100644
index 974738d..0000000
--- a/src/main/resources/spotbugs-exclude.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
From a50029d31564f88c05c99da61dae7b612fc42ce1 Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Mon, 27 Oct 2025 13:30:58 +0000
Subject: [PATCH 06/17] Set realistic code-review coverage gates
---
pom.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pom.xml b/pom.xml
index 76070fe..aec75d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -140,8 +140,8 @@
false
- 0.0
- 0.0
+ 0.68
+ 0.45
From 50de3fdfc9f1ce0d22d37791a4532e56e50cd379 Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Mon, 27 Oct 2025 14:01:46 +0000
Subject: [PATCH 07/17] Enable AsciiDoc section numbering in license
---
LICENSE.adoc | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/LICENSE.adoc b/LICENSE.adoc
index 9ad6f2c..817b602 100644
--- a/LICENSE.adoc
+++ b/LICENSE.adoc
@@ -1,3 +1,8 @@
+= Posix Module License
+:sectnums:
+:encoding: ISO-8859-1
+:lang: en-GB
+
== Copyright 2016 higherfrequencytrading.com
Licensed under the *Apache License, Version 2.0* (the "License"); you may not use this file except in compliance with the License.
From 30b2b241a57157d9b93420ac8efb3489ea056654 Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Mon, 27 Oct 2025 14:29:56 +0000
Subject: [PATCH 08/17] Restore Java 8 reflection compatibility
---
.../posix/internal/PosixAPIHolderTest.java | 4 +-
.../posix/internal/ReflectionAccess.java | 49 +++++++++++++++++++
.../posix/internal/jna/JNAPosixAPITest.java | 6 +--
3 files changed, 53 insertions(+), 6 deletions(-)
create mode 100644 src/test/java/net/openhft/posix/internal/ReflectionAccess.java
diff --git a/src/test/java/net/openhft/posix/internal/PosixAPIHolderTest.java b/src/test/java/net/openhft/posix/internal/PosixAPIHolderTest.java
index 057de55..80d3f43 100644
--- a/src/test/java/net/openhft/posix/internal/PosixAPIHolderTest.java
+++ b/src/test/java/net/openhft/posix/internal/PosixAPIHolderTest.java
@@ -76,9 +76,7 @@ private static void swapPlatform(Platform platform) throws Exception {
private static Field singletonField() throws Exception {
Class> holder = Class.forName("jnr.ffi.Platform$SingletonHolder");
Field field = holder.getDeclaredField("PLATFORM");
- if (!field.canAccess(null)) {
- field.setAccessible(true);
- }
+ ReflectionAccess.ensureAccessible(field, null);
return field;
}
diff --git a/src/test/java/net/openhft/posix/internal/ReflectionAccess.java b/src/test/java/net/openhft/posix/internal/ReflectionAccess.java
new file mode 100644
index 0000000..0c42a05
--- /dev/null
+++ b/src/test/java/net/openhft/posix/internal/ReflectionAccess.java
@@ -0,0 +1,49 @@
+package net.openhft.posix.internal;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * Reflection helper that supports both Java 8 and post-Java 9 runtimes when
+ * checking accessibility. Java 8 lacks {@code AccessibleObject.canAccess},
+ * so we fall back to the legacy {@code isAccessible()} API.
+ */
+public final class ReflectionAccess {
+ private static final Method CAN_ACCESS_METHOD = locateCanAccess();
+
+ private ReflectionAccess() {
+ }
+
+ public static void ensureAccessible(Field field, Object target) {
+ if (!canAccess(field, target)) {
+ field.setAccessible(true);
+ }
+ }
+
+ private static boolean canAccess(Field field, Object target) {
+ Method method = CAN_ACCESS_METHOD;
+ if (method != null) {
+ try {
+ return (Boolean) method.invoke(field, target);
+ } catch (ReflectiveOperationException e) {
+ throw new IllegalStateException("Failed to invoke AccessibleObject.canAccess", e);
+ }
+ }
+ return legacyIsAccessible(field);
+ }
+
+ @SuppressWarnings("deprecation")
+ private static boolean legacyIsAccessible(AccessibleObject object) {
+ return object.isAccessible();
+ }
+
+ private static Method locateCanAccess() {
+ try {
+ return AccessibleObject.class.getMethod("canAccess", Object.class);
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+}
+
diff --git a/src/test/java/net/openhft/posix/internal/jna/JNAPosixAPITest.java b/src/test/java/net/openhft/posix/internal/jna/JNAPosixAPITest.java
index defd347..e35abbd 100644
--- a/src/test/java/net/openhft/posix/internal/jna/JNAPosixAPITest.java
+++ b/src/test/java/net/openhft/posix/internal/jna/JNAPosixAPITest.java
@@ -7,6 +7,8 @@
import java.lang.reflect.Field;
import java.util.ArrayList;
+import net.openhft.posix.internal.ReflectionAccess;
+
import static net.openhft.posix.internal.UnsafeMemory.UNSAFE;
import static org.junit.Assert.*;
@@ -40,9 +42,7 @@ public void mmapUsesNullPointerForZeroAddress() throws Exception {
private static void injectStub(JNAPosixAPI api, JNAPosixInterface stub) throws Exception {
Field field = JNAPosixAPI.class.getDeclaredField("jna");
- if (!field.canAccess(api)) {
- field.setAccessible(true);
- }
+ ReflectionAccess.ensureAccessible(field, api);
long offset = UNSAFE.objectFieldOffset(field);
UNSAFE.putObject(api, offset, stub);
}
From 47857a7f9b7aa37dd92fc6858baccbcbe47c4ead Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Mon, 27 Oct 2025 15:46:30 +0000
Subject: [PATCH 09/17] Use Java 8 compatible SpotBugs annotations
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index aec75d2..da8389a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,7 +48,7 @@
com.github.spotbugs
spotbugs-annotations
- 4.9.0
+ 4.8.6
provided
From 6607ab3463f98dd2dbe267dd1b0d78a73a882151 Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Tue, 28 Oct 2025 09:44:06 +0000
Subject: [PATCH 10/17] Enforce higher coverage gates
---
pom.xml | 4 +-
.../java/net/openhft/posix/LockfFlagTest.java | 16 +
.../posix/PosixRuntimeExceptionTest.java | 31 +
.../jnr/JNRPosixAPINonNativeTest.java | 854 ++++++++++++++++++
.../internal/jnr/WinJNRPosixAPITest.java | 139 +++
5 files changed, 1042 insertions(+), 2 deletions(-)
create mode 100644 src/test/java/net/openhft/posix/LockfFlagTest.java
create mode 100644 src/test/java/net/openhft/posix/PosixRuntimeExceptionTest.java
create mode 100644 src/test/java/net/openhft/posix/internal/jnr/JNRPosixAPINonNativeTest.java
create mode 100644 src/test/java/net/openhft/posix/internal/jnr/WinJNRPosixAPITest.java
diff --git a/pom.xml b/pom.xml
index da8389a..544fa7b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -140,8 +140,8 @@
false
- 0.68
- 0.45
+ 0.89
+ 0.725
diff --git a/src/test/java/net/openhft/posix/LockfFlagTest.java b/src/test/java/net/openhft/posix/LockfFlagTest.java
new file mode 100644
index 0000000..e99de02
--- /dev/null
+++ b/src/test/java/net/openhft/posix/LockfFlagTest.java
@@ -0,0 +1,16 @@
+package net.openhft.posix;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class LockfFlagTest {
+
+ @Test
+ public void valuesMatchNativeConstants() {
+ assertEquals(0, LockfFlag.F_ULOCK.value());
+ assertEquals(1, LockfFlag.F_LOCK.value());
+ assertEquals(2, LockfFlag.F_TLOCK.value());
+ assertEquals(3, LockfFlag.F_TEST.value());
+ }
+}
diff --git a/src/test/java/net/openhft/posix/PosixRuntimeExceptionTest.java b/src/test/java/net/openhft/posix/PosixRuntimeExceptionTest.java
new file mode 100644
index 0000000..b9d94a2
--- /dev/null
+++ b/src/test/java/net/openhft/posix/PosixRuntimeExceptionTest.java
@@ -0,0 +1,31 @@
+package net.openhft.posix;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+public class PosixRuntimeExceptionTest {
+
+ @Test
+ public void capturesMessageOnly() {
+ PosixRuntimeException ex = new PosixRuntimeException("failed");
+ assertEquals("failed", ex.getMessage());
+ assertEquals(0, ex.errno());
+ }
+
+ @Test
+ public void capturesCause() {
+ IllegalStateException root = new IllegalStateException("root");
+ PosixRuntimeException ex = new PosixRuntimeException(root);
+ assertSame(root, ex.getCause());
+ assertEquals(0, ex.errno());
+ }
+
+ @Test
+ public void capturesErrnoAlongsideMessage() {
+ PosixRuntimeException ex = new PosixRuntimeException("fail", 22);
+ assertEquals("fail", ex.getMessage());
+ assertEquals(22, ex.errno());
+ }
+}
diff --git a/src/test/java/net/openhft/posix/internal/jnr/JNRPosixAPINonNativeTest.java b/src/test/java/net/openhft/posix/internal/jnr/JNRPosixAPINonNativeTest.java
new file mode 100644
index 0000000..2e6bc14
--- /dev/null
+++ b/src/test/java/net/openhft/posix/internal/jnr/JNRPosixAPINonNativeTest.java
@@ -0,0 +1,854 @@
+package net.openhft.posix.internal.jnr;
+
+import jnr.constants.platform.Errno;
+import jnr.ffi.Pointer;
+import net.openhft.posix.MAdviseFlag;
+import net.openhft.posix.MSyncFlag;
+import net.openhft.posix.MclFlag;
+import net.openhft.posix.PosixRuntimeException;
+import net.openhft.posix.internal.UnsafeMemory;
+import net.openhft.posix.internal.core.Jvm;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.IntSupplier;
+
+import static org.junit.Assert.*;
+
+public class JNRPosixAPINonNativeTest {
+
+ private String originalVmVendor;
+
+ @Before
+ public void captureVmVendor() throws Exception {
+ originalVmVendor = readStaticString(Jvm.class, "VM_VENDOR");
+ }
+
+ @After
+ public void restoreVmVendor() throws Exception {
+ swapStaticString(Jvm.class, "VM_VENDOR", originalVmVendor);
+ }
+
+ @Test
+ public void fileLockerAcquiresAndReleases() throws Exception {
+ List operations = new ArrayList<>();
+ JNRPosixInterface stub = new BaseStub() {
+ private final Deque results = new ArrayDeque<>(Arrays.asList(0, 0));
+
+ @Override
+ public int flock(int fd, int operation) {
+ operations.add(operation);
+ return results.removeFirst();
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ try (JNRPosixAPI.FileLocker locker = api.new FileLocker(11)) {
+ locker.ensureAcquired();
+ }
+
+ assertEquals(Arrays.asList(JNRPosixAPI.LOCK_EX, JNRPosixAPI.LOCK_UN), operations);
+ }
+
+ @Test
+ public void fileLockerFailsToAcquire() throws Exception {
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int flock(int fd, int operation) {
+ return 1;
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ try {
+ api.new FileLocker(7);
+ fail("Expected IOException");
+ } catch (IOException expected) {
+ assertEquals("Failed to acquire lock", expected.getMessage());
+ }
+ }
+
+ @Test
+ public void fileLockerFailsToRelease() throws Exception {
+ JNRPosixInterface stub = new BaseStub() {
+ private final Deque results = new ArrayDeque<>(Arrays.asList(0, 1));
+
+ @Override
+ public int flock(int fd, int operation) {
+ return results.removeFirst();
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ JNRPosixAPI.FileLocker locker = api.new FileLocker(3);
+ try {
+ locker.close();
+ fail("Expected IOException");
+ } catch (IOException expected) {
+ assertEquals("Failed to release lock", expected.getMessage());
+ }
+ }
+
+ @Test
+ public void fallocateFallsBackToPosix() throws Exception {
+ AtomicInteger posixCalls = new AtomicInteger();
+ List flockOps = new ArrayList<>();
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int fallocate64(int fd, int mode, long offset, long length) {
+ throw new UnsatisfiedLinkError("missing");
+ }
+
+ @Override
+ public int fallocate(int fd, int mode, long offset, long length) {
+ return -1;
+ }
+
+ @Override
+ public int posix_fallocate(int fd, long offset, long length) {
+ posixCalls.incrementAndGet();
+ return 0;
+ }
+
+ @Override
+ public int flock(int fd, int operation) {
+ flockOps.add(operation);
+ return 0;
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ int result = api.fallocate(17, 0, 1L, 2L);
+
+ assertEquals(0, result);
+ assertEquals(1, posixCalls.get());
+ assertEquals(Arrays.asList(JNRPosixAPI.LOCK_EX, JNRPosixAPI.LOCK_UN), flockOps);
+ }
+
+ @Test
+ public void fallocateReturnsFailureWhenPosixFallbackFails() throws Exception {
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int fallocate64(int fd, int mode, long offset, long length) {
+ throw new UnsatisfiedLinkError("missing");
+ }
+
+ @Override
+ public int fallocate(int fd, int mode, long offset, long length) {
+ return -1;
+ }
+
+ @Override
+ public int posix_fallocate(int fd, long offset, long length) {
+ return -1;
+ }
+
+ @Override
+ public int flock(int fd, int operation) {
+ return 0;
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ int result = api.fallocate(21, 0, 4L, 8L);
+ assertEquals(-1, result);
+ }
+
+ @Test
+ public void fallocatePropagatesWhenModeNonZero() throws Exception {
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int fallocate(int fd, int mode, long offset, long length) {
+ throw new IllegalStateException("boom");
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ try {
+ api.fallocate(30, 1, 0L, 1L);
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException expected) {
+ assertEquals("boom", expected.getMessage());
+ }
+ }
+
+ @Test
+ public void mlockReturnsTrueOnSuccess() throws Exception {
+ AtomicBoolean called = new AtomicBoolean();
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int mlock(long addr, long length) {
+ called.set(true);
+ return 0;
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ assertTrue(api.mlock(10L, 32L));
+ assertTrue(called.get());
+ }
+
+ @Test
+ public void mlockReturnsFalseOnEnomem() throws Exception {
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int mlock(long addr, long length) {
+ return Errno.ENOMEM.intValue();
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ assertFalse(api.mlock(5L, 16L));
+ }
+
+ @Test
+ public void mlockSkipsOnAzul() throws Exception {
+ swapStaticString(Jvm.class, "VM_VENDOR", "Azul Systems Prime");
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int mlock(long addr, long length) {
+ throw new AssertionError("Should not be called");
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ assertTrue(api.mlock(7L, 9L));
+ }
+
+ @Test
+ public void mlock2UsesSyscallWhenRequested() throws Exception {
+ AtomicBoolean syscallUsed = new AtomicBoolean();
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int mlock(long addr, long length) {
+ throw new AssertionError("Should not call mlock");
+ }
+
+ @Override
+ public int syscall(int number, long arg1, long arg2, int arg3) {
+ syscallUsed.set(true);
+ return 0;
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ assertTrue(api.mlock2(1L, 2L, true));
+ assertTrue(syscallUsed.get());
+ }
+
+ @Test
+ public void mlock2ReturnsFalseOnEnomem() throws Exception {
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int syscall(int number, long arg1, long arg2, int arg3) {
+ return Errno.ENOMEM.intValue();
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ assertFalse(api.mlock2(1L, 2L, true));
+ }
+
+ @Test
+ public void mlock2FallsBackWhenLockOnFaultFalse() throws Exception {
+ AtomicBoolean mlockCalled = new AtomicBoolean();
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int mlock(long addr, long length) {
+ mlockCalled.set(true);
+ return 0;
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ assertTrue(api.mlock2(3L, 4L, false));
+ assertTrue(mlockCalled.get());
+ }
+
+ @Test
+ public void msyncDelegatesToInterface() throws Exception {
+ AtomicBoolean invoked = new AtomicBoolean();
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int msync(long address, long length, int flags) {
+ invoked.set(true);
+ assertEquals(10L, address);
+ assertEquals(20L, length);
+ assertEquals(MSyncFlag.MS_ASYNC.value(), flags);
+ return 0;
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ assertEquals(0, api.msync(10L, 20L, MSyncFlag.MS_ASYNC.value()));
+ assertTrue(invoked.get());
+ }
+
+ @Test
+ public void madviseDelegatesToInterface() throws Exception {
+ AtomicBoolean invoked = new AtomicBoolean();
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int madvise(long addr, long length, int advise) {
+ invoked.set(true);
+ assertEquals(12L, addr);
+ assertEquals(24L, length);
+ assertEquals(MAdviseFlag.MADV_SEQUENTIAL.value(), advise);
+ return 0;
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ assertEquals(0, api.madvise(12L, 24L, MAdviseFlag.MADV_SEQUENTIAL.value()));
+ assertTrue(invoked.get());
+ }
+
+ @Test
+ public void schedSetAffinityDelegates() throws Exception {
+ AtomicBoolean called = new AtomicBoolean();
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int sched_setaffinity(int pid, int cpusetsize, Pointer mask) {
+ called.set(true);
+ assertNotNull(mask);
+ return 0;
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ assertEquals(0, api.sched_setaffinity(4, 8, 0xFFL));
+ assertTrue(called.get());
+ }
+
+ @Test
+ public void schedSetAffinityThrowsWhenNativeFails() throws Exception {
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int sched_setaffinity(int pid, int cpusetsize, Pointer mask) {
+ return -1;
+ }
+
+ @Override
+ public String strerror(int errno) {
+ return "affinity";
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ try {
+ api.sched_setaffinity(5, 8, 0x1L);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(expected.getMessage().contains("affinity"));
+ }
+ }
+
+ @Test
+ public void schedGetAffinityThrowsWhenNativeFails() throws Exception {
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int sched_getaffinity(int pid, int cpusetsize, Pointer mask) {
+ return -1;
+ }
+
+ @Override
+ public String strerror(int errno) {
+ return "affinity";
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ try {
+ api.sched_getaffinity(6, 8, 0x2L);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(expected.getMessage().contains("affinity"));
+ }
+ }
+
+ @Test
+ public void gettidThrowsWhenSupplierNegative() throws Exception {
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public String strerror(int errno) {
+ return "gettid";
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+ setField(api, "gettid", (IntSupplier) () -> -3);
+
+ try {
+ api.gettid();
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(expected.getMessage().contains("gettid"));
+ }
+ }
+
+ @Test
+ public void getNProcsConfCachesValue() throws Exception {
+ AtomicInteger calls = new AtomicInteger();
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int get_nprocs_conf() {
+ return 8 + calls.getAndIncrement();
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ assertEquals(8, api.get_nprocs_conf());
+ assertEquals(8, api.get_nprocs_conf());
+ assertEquals(1, calls.get());
+ }
+
+ @Test
+ public void clockGettimeThrowsOnNativeFailure() throws Exception {
+ List allocated = new ArrayList<>();
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public long malloc(long size) {
+ long ptr = UnsafeMemory.UNSAFE.allocateMemory(size);
+ allocated.add(ptr);
+ return ptr;
+ }
+
+ @Override
+ public void free(long ptr) {
+ UnsafeMemory.UNSAFE.freeMemory(ptr);
+ allocated.remove(ptr);
+ }
+
+ @Override
+ public int clock_gettime(int clockId, long ptr) {
+ return -1;
+ }
+
+ @Override
+ public String strerror(int errno) {
+ return "clock";
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ try {
+ api.clock_gettime(1);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(expected.getMessage().contains("clock"));
+ }
+ assertTrue(allocated.isEmpty());
+ }
+
+ @Test
+ public void clockGettimeReturnsValue() throws Exception {
+ List allocated = new ArrayList<>();
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public long malloc(long size) {
+ long ptr = UnsafeMemory.UNSAFE.allocateMemory(size);
+ allocated.add(ptr);
+ return ptr;
+ }
+
+ @Override
+ public void free(long ptr) {
+ UnsafeMemory.UNSAFE.freeMemory(ptr);
+ allocated.remove(ptr);
+ }
+
+ @Override
+ public int clock_gettime(int clockId, long ptr) {
+ UnsafeMemory.UNSAFE.putLong(ptr, 2L);
+ UnsafeMemory.UNSAFE.putInt(ptr + 8, 50);
+ return 0;
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ long nanos = api.clock_gettime(4);
+ assertEquals(2_000_000_000L + 50, nanos);
+ assertTrue(allocated.isEmpty());
+ }
+
+ @Test
+ public void gettimeofdayReturnsMicros() throws Exception {
+ List allocated = new ArrayList<>();
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public long malloc(long size) {
+ long ptr = UnsafeMemory.UNSAFE.allocateMemory(size);
+ allocated.add(ptr);
+ return ptr;
+ }
+
+ @Override
+ public void free(long ptr) {
+ UnsafeMemory.UNSAFE.freeMemory(ptr);
+ allocated.remove(ptr);
+ }
+
+ @Override
+ public int gettimeofday(long timeval, long alwaysNull) {
+ UnsafeMemory.UNSAFE.putLong(timeval, 7L);
+ UnsafeMemory.UNSAFE.putLong(timeval + 8, 250_000L);
+ return 0;
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ long micros = api.gettimeofday();
+ assertEquals(7_000_000L + 250_000L, micros);
+ assertTrue(allocated.isEmpty());
+ }
+
+ @Test
+ public void mlockallThrowsOnFailure() throws Exception {
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int mlockall(int flags) {
+ return -1;
+ }
+
+ @Override
+ public String strerror(int errno) {
+ return "mlockall";
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ try {
+ api.mlockall(0x123);
+ fail("Expected PosixRuntimeException");
+ } catch (IllegalArgumentException | PosixRuntimeException expected) {
+ assertTrue(expected.getMessage().contains("mlockall"));
+ }
+ }
+
+ @Test
+ public void mmapReturnsPointer() throws Exception {
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public long mmap(Pointer addr, long length, int prot, int flags, int fd, long offset) {
+ return 123L;
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ assertEquals(123L, api.mmap(0L, 64L, 1, 2, 3, 4L));
+ }
+
+ @Test
+ public void mmapThrowsOnFailure() throws Exception {
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public long mmap(Pointer addr, long length, int prot, int flags, int fd, long offset) {
+ return 0L;
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+
+ try {
+ api.mmap(1L, 64L, 1, 2, 3, 4L);
+ fail("Expected PosixRuntimeException");
+ } catch (PosixRuntimeException expected) {
+ assertFalse(expected.getMessage().isEmpty());
+ }
+ }
+
+ @Test
+ public void getGettidFallsBackToSyscallOn32Bit() throws Exception {
+ boolean original32 = readStaticBoolean(UnsafeMemory.class, "IS32BIT");
+ boolean original64 = readStaticBoolean(UnsafeMemory.class, "IS64BIT");
+ swapStaticBoolean(UnsafeMemory.class, "IS32BIT", true);
+ swapStaticBoolean(UnsafeMemory.class, "IS64BIT", false);
+ try {
+ AtomicInteger calls = new AtomicInteger();
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int gettid() {
+ throw new UnsatisfiedLinkError("no symbol");
+ }
+
+ @Override
+ public int syscall(int number) {
+ assertEquals(224, number);
+ calls.incrementAndGet();
+ return 99;
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+ Method method = JNRPosixAPI.class.getDeclaredMethod("getGettid");
+ method.setAccessible(true);
+ IntSupplier supplier = (IntSupplier) method.invoke(api);
+ assertEquals(99, supplier.getAsInt());
+ assertEquals(1, calls.get());
+ } finally {
+ swapStaticBoolean(UnsafeMemory.class, "IS32BIT", original32);
+ swapStaticBoolean(UnsafeMemory.class, "IS64BIT", original64);
+ }
+ }
+
+ @Test
+ public void mlockallLogsWhenDumpEnabled() throws Exception {
+ boolean originalDump = swapStaticBoolean(JNRPosixAPI.class, "MOCKALL_DUMP", true);
+ String previousProperty = System.getProperty("mlockall.dump");
+ System.setProperty("mlockall.dump", "true");
+ AtomicBoolean firstCall = new AtomicBoolean(true);
+ AtomicInteger calls = new AtomicInteger();
+ JNRPosixInterface stub = new BaseStub() {
+ @Override
+ public int mlock(long addr, long length) {
+ calls.incrementAndGet();
+ if (firstCall.getAndSet(false))
+ return -1;
+ return 0;
+ }
+
+ @Override
+ public int mlockall(int flags) {
+ throw new AssertionError("mlockall should not be called in fallback");
+ }
+ };
+ JNRPosixAPI api = newApi(stub);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream originalOut = System.out;
+ System.setOut(new PrintStream(baos));
+ try {
+ api.mlockall(MclFlag.MclCurrent.code());
+ } finally {
+ System.setOut(originalOut);
+ if (previousProperty == null) {
+ System.clearProperty("mlockall.dump");
+ } else {
+ System.setProperty("mlockall.dump", previousProperty);
+ }
+ swapStaticBoolean(JNRPosixAPI.class, "MOCKALL_DUMP", originalDump);
+ }
+ assertTrue(calls.get() > 0);
+ assertTrue(baos.toString().length() > 0);
+ }
+
+ private static JNRPosixAPI newApi(JNRPosixInterface stub) throws Exception {
+ JNRPosixAPI api = (JNRPosixAPI) UnsafeMemory.UNSAFE.allocateInstance(JNRPosixAPI.class);
+ setField(api, "jnr", stub);
+ setField(api, "gettid", (IntSupplier) () -> 1);
+ return api;
+ }
+
+ private static void setField(Object target, String fieldName, Object value) throws Exception {
+ Field field = JNRPosixAPI.class.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(target, value);
+ }
+
+ private static boolean swapStaticBoolean(Class> type, String fieldName, boolean newValue) throws Exception {
+ Field field = type.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Object base = UnsafeMemory.UNSAFE.staticFieldBase(field);
+ long offset = UnsafeMemory.UNSAFE.staticFieldOffset(field);
+ boolean old = UnsafeMemory.UNSAFE.getBoolean(base, offset);
+ UnsafeMemory.UNSAFE.putBoolean(base, offset, newValue);
+ return old;
+ }
+
+ private static boolean readStaticBoolean(Class> type, String fieldName) throws Exception {
+ Field field = type.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Object base = UnsafeMemory.UNSAFE.staticFieldBase(field);
+ long offset = UnsafeMemory.UNSAFE.staticFieldOffset(field);
+ return UnsafeMemory.UNSAFE.getBoolean(base, offset);
+ }
+
+ private static String swapStaticString(Class> type, String fieldName, Object newValue) throws Exception {
+ Field field = type.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Object base = UnsafeMemory.UNSAFE.staticFieldBase(field);
+ long offset = UnsafeMemory.UNSAFE.staticFieldOffset(field);
+ Object old = UnsafeMemory.UNSAFE.getObject(base, offset);
+ UnsafeMemory.UNSAFE.putObject(base, offset, newValue);
+ return old == null ? null : old.toString();
+ }
+
+ private static String readStaticString(Class> type, String fieldName) throws Exception {
+ Field field = type.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Object base = UnsafeMemory.UNSAFE.staticFieldBase(field);
+ long offset = UnsafeMemory.UNSAFE.staticFieldOffset(field);
+ Object value = UnsafeMemory.UNSAFE.getObject(base, offset);
+ return value == null ? null : value.toString();
+ }
+
+ private static class BaseStub implements JNRPosixInterface {
+ @Override
+ public int open(CharSequence path, int flags, int perm) {
+ throw unsupported();
+ }
+
+ @Override
+ public long read(int fd, long dst, long len) {
+ throw unsupported();
+ }
+
+ @Override
+ public long write(int fd, long src, long len) {
+ throw unsupported();
+ }
+
+ @Override
+ public long lseek(int fd, long offset, int whence) {
+ throw unsupported();
+ }
+
+ @Override
+ public int lockf(int fd, int cmd, long len) {
+ throw unsupported();
+ }
+
+ @Override
+ public int flock(int fd, int operation) {
+ throw unsupported();
+ }
+
+ @Override
+ public int ftruncate(int fd, long offset) {
+ throw unsupported();
+ }
+
+ @Override
+ public int fallocate(int fd, int mode, long offset, long length) {
+ throw unsupported();
+ }
+
+ @Override
+ public int fallocate64(int fd, int mode, long offset, long length) {
+ throw unsupported();
+ }
+
+ @Override
+ public int posix_fallocate(int fd, long offset, long length) {
+ throw unsupported();
+ }
+
+ @Override
+ public int close(int fd) {
+ throw unsupported();
+ }
+
+ @Override
+ public int madvise(long addr, long length, int advise) {
+ throw unsupported();
+ }
+
+ @Override
+ public long mmap(Pointer addr, long length, int prot, int flags, int fd, long offset) {
+ throw unsupported();
+ }
+
+ @Override
+ public int munmap(long addr, long length) {
+ throw unsupported();
+ }
+
+ @Override
+ public int msync(long address, long length, int flags) {
+ throw unsupported();
+ }
+
+ @Override
+ public int gettimeofday(long timeval, long alwaysNull) {
+ throw unsupported();
+ }
+
+ @Override
+ public long malloc(long size) {
+ throw unsupported();
+ }
+
+ @Override
+ public void free(long ptr) {
+ throw unsupported();
+ }
+
+ @Override
+ public int get_nprocs() {
+ throw unsupported();
+ }
+
+ @Override
+ public int get_nprocs_conf() {
+ throw unsupported();
+ }
+
+ @Override
+ public int sched_setaffinity(int pid, int cpusetsize, Pointer mask) {
+ throw unsupported();
+ }
+
+ @Override
+ public int sched_getaffinity(int pid, int cpusetsize, Pointer mask) {
+ throw unsupported();
+ }
+
+ @Override
+ public int getpid() {
+ throw unsupported();
+ }
+
+ @Override
+ public int gettid() {
+ throw unsupported();
+ }
+
+ @Override
+ public String strerror(int errno) {
+ throw unsupported();
+ }
+
+ @Override
+ public int clock_gettime(int clockId, long ptr) {
+ throw unsupported();
+ }
+
+ @Override
+ public int mlock(long addr, long length) {
+ throw unsupported();
+ }
+
+ @Override
+ public int mlock2(long addr, long length, int flags) {
+ throw unsupported();
+ }
+
+ @Override
+ public int mlockall(int flags) {
+ throw unsupported();
+ }
+
+ @Override
+ public int syscall(int number) {
+ throw unsupported();
+ }
+
+ @Override
+ public int syscall(int number, long arg1, long arg2, int arg3) {
+ throw unsupported();
+ }
+
+ private UnsupportedOperationException unsupported() {
+ return new UnsupportedOperationException("not expected");
+ }
+ }
+}
diff --git a/src/test/java/net/openhft/posix/internal/jnr/WinJNRPosixAPITest.java b/src/test/java/net/openhft/posix/internal/jnr/WinJNRPosixAPITest.java
new file mode 100644
index 0000000..5288613
--- /dev/null
+++ b/src/test/java/net/openhft/posix/internal/jnr/WinJNRPosixAPITest.java
@@ -0,0 +1,139 @@
+package net.openhft.posix.internal.jnr;
+
+import net.openhft.posix.internal.UnsafeMemory;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+public class WinJNRPosixAPITest {
+
+ @Test
+ public void delegatesToNativeInterfaces() throws Exception {
+ RecordingWin win = new RecordingWin();
+ RecordingKernel kernel = new RecordingKernel();
+
+ WinJNRPosixAPI api = (WinJNRPosixAPI) UnsafeMemory.UNSAFE.allocateInstance(WinJNRPosixAPI.class);
+ setField(api, "jnr", win);
+ setField(api, "kernel32", kernel);
+
+ assertEquals(42, api.open("path", 1, 2));
+ assertEquals(64L, api.lseek(3, 4L, 5));
+ assertEquals(5L, api.read(6, 7L, 8L));
+ assertEquals(6L, api.write(9, 10L, 11L));
+ assertEquals(12, api.close(13));
+ assertEquals(1234, api.getpid());
+ assertEquals(9876, api.gettid());
+ assertEquals("error", api.strerror(99));
+
+ long ptr = api.malloc(32);
+ assertNotEquals(0L, ptr);
+ api.free(ptr);
+
+ assertEquals(0, api.madvise(1L, 2L, 3));
+ assertEquals(0, api.msync(1L, 2L, 3));
+ assertEquals(0, api.fallocate(1, 2, 3L, 4L));
+ assertEquals(0, api.ftruncate(1, 2L));
+ assertEquals(0L, api.mmap(1L, 2L, 3, 4, 5, 6L));
+ assertEquals(0, api.munmap(1L, 2L));
+ long timeval = UnsafeMemory.UNSAFE.allocateMemory(16);
+ try {
+ assertEquals(0, api.gettimeofday(timeval));
+ } finally {
+ UnsafeMemory.UNSAFE.freeMemory(timeval);
+ }
+ assertTrue(api.clock_gettime() > 0);
+ assertTrue(api.clock_gettime(1) > 0);
+ assertEquals(-1, api.lockf(1, 2, 3L));
+ assertEquals(-1, api.sched_setaffinity(1, 2, 3L));
+ assertEquals(-1, api.sched_getaffinity(1, 2, 3L));
+ assertTrue(api.lastError() >= 0);
+ assertEquals(Runtime.getRuntime().availableProcessors(), api.get_nprocs_conf());
+ assertEquals(api.get_nprocs_conf(), api.get_nprocs());
+
+ assertEquals(List.of("open", "lseek", "read", "write", "close", "pid", "strerror"), win.calls);
+ assertEquals(List.of("tid"), kernel.calls);
+ }
+
+ private static void setField(Object target, String fieldName, Object value) throws Exception {
+ Field field = WinJNRPosixAPI.class.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(target, value);
+ }
+
+ private static final class RecordingWin implements WinJNRPosixInterface {
+ final List calls = new ArrayList<>();
+
+ @Override
+ public long malloc(long size) {
+ calls.add("malloc");
+ return UnsafeMemory.UNSAFE.allocateMemory(size);
+ }
+
+ @Override
+ public void free(long ptr) {
+ calls.add("free");
+ UnsafeMemory.UNSAFE.freeMemory(ptr);
+ }
+
+ @Override
+ public int _close(int fd) {
+ calls.add("close");
+ return 12;
+ }
+
+ @Override
+ public int _open(CharSequence path, int flags, int perm) {
+ calls.add("open");
+ return 42;
+ }
+
+ @Override
+ public long _lseeki64(int fd, long offset, int origin) {
+ calls.add("lseek");
+ return 64L;
+ }
+
+ @Override
+ public long _read(int fd, long dst, long len) {
+ calls.add("read");
+ return 5L;
+ }
+
+ @Override
+ public long _write(int fd, long src, long len) {
+ calls.add("write");
+ return 6L;
+ }
+
+ @Override
+ public int _getpid() {
+ calls.add("pid");
+ return 1234;
+ }
+
+ @Override
+ public String strerror(int errno) {
+ calls.add("strerror");
+ return "error";
+ }
+ }
+
+ private static final class RecordingKernel implements Kernel32JNRInterface {
+ final List calls = new ArrayList<>();
+
+ @Override
+ public int GetCurrentThreadId() {
+ calls.add("tid");
+ return 9876;
+ }
+
+ @Override
+ public void GetNativeSystemInfo(long addr) {
+ calls.add("sysinfo");
+ }
+ }
+}
From bf2b63692e57e00b97064a41bac74140d9469741 Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Tue, 28 Oct 2025 10:21:38 +0000
Subject: [PATCH 11/17] Refactor code comments and formatting for clarity and
consistency
---
AGENTS.md | 80 +++++++++++--------
README.adoc | 1 -
pom.xml | 15 ++--
src/main/adoc/decision-log.adoc | 28 +++----
src/main/adoc/project-requirements.adoc | 6 +-
src/main/config/spotbugs-exclude.xml | 76 ++++++++++--------
src/main/java/net/openhft/posix/ClockId.java | 48 ++++++++---
.../java/net/openhft/posix/LockfFlag.java | 20 +++--
.../java/net/openhft/posix/MAdviseFlag.java | 72 ++++++++++++-----
src/main/java/net/openhft/posix/MMapFlag.java | 12 ++-
src/main/java/net/openhft/posix/MMapProt.java | 30 +++++--
.../java/net/openhft/posix/MSyncFlag.java | 16 +++-
src/main/java/net/openhft/posix/Mapping.java | 37 ++++++---
src/main/java/net/openhft/posix/MclFlag.java | 20 +++--
src/main/java/net/openhft/posix/OpenFlag.java | 52 +++++++++---
.../openhft/posix/PosixRuntimeException.java | 8 +-
src/main/java/net/openhft/posix/ProcMaps.java | 4 +-
.../java/net/openhft/posix/WhenceFlag.java | 20 +++--
.../net/openhft/posix/internal/core/Jvm.java | 4 +-
.../net/openhft/posix/internal/core/OS.java | 4 +-
.../posix/internal/jnr/JNRPosixAPI.java | 18 ++---
.../posix/internal/jnr/JNRPosixInterface.java | 4 +
.../openhft/posix/internal/package-info.java | 4 +-
.../java/net/openhft/posix/package-info.java | 4 +-
.../posix/internal/PosixAPIHolderTest.java | 3 +-
.../posix/internal/jna/JNAPosixAPITest.java | 7 +-
.../jnr/JNRPosixAPINonNativeTest.java | 6 +-
.../posix/internal/jnr/JNRPosixAPITest.java | 1 +
.../posix/internal/raw/RawPosixAPITest.java | 2 +-
29 files changed, 394 insertions(+), 208 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index 7f8a075..620cde4 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. |
+| **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. |
## 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.
@@ -60,7 +60,8 @@ See the [Project Requirements](src/main/adoc/project-requirements.adoc) for deta
## 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
+ 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.
## 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/README.adoc b/README.adoc
index 40f2a5a..aeeeae5 100644
--- a/README.adoc
+++ b/README.adoc
@@ -118,7 +118,6 @@ Add JVM arg:
====
[%collapsible]
-
== Further reading
* link:src/main/adoc/project-requirements.adoc[Functional requirements]
diff --git a/pom.xml b/pom.xml
index 544fa7b..03643ce 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
net.openhft
java-parent-pom
1.27ea1
-
+
posix
@@ -16,7 +16,7 @@
OpenHFT Java Posix APIs
jar
-
+
3.6.0
10.26.1
4.9.8.1
@@ -24,9 +24,9 @@
3.28.0
0.8.14
0.80
- 0.70
- 1.23ea6
-
+ 0.70
+ 1.23ea6
+
@@ -201,8 +201,9 @@
Max
Low
true
- ${project.basedir}/src/main/config/spotbugs-exclude.xml
-
+ ${project.basedir}/src/main/config/spotbugs-exclude.xml
+
+
com.h3xstream.findsecbugs
findsecbugs-plugin
diff --git a/src/main/adoc/decision-log.adoc b/src/main/adoc/decision-log.adoc
index a0b9600..d491311 100644
--- a/src/main/adoc/decision-log.adoc
+++ b/src/main/adoc/decision-log.adoc
@@ -17,11 +17,11 @@
| ALL-OPS-004 | Nine-Box Tag Taxonomy Adoption | 2025-05-26 | OPS
|===
-
== ALL-DOC-001 Language & Character-Set Policy
*Context*::
-Chronicle teams span multiple regions. Mixed US/UK spelling and occasional UTF-8 artefacts caused build failures in binary wire-format tests and confused spell-checkers.
+Chronicle teams span multiple regions.
+Mixed US/UK spelling and occasional UTF-8 artefacts caused build failures in binary wire-format tests and confused spell-checkers.
*Decision*::
* Use **British English** spelling for prose/code except fixed US technical spellings (`synchronized`, `Serializable`).
@@ -38,7 +38,9 @@ Chronicle teams span multiple regions. Mixed US/UK spelling and occasional UTF-8
== POSIX-FN-002 Provider Fallback Order
*Context*::
-The Posix façade must run on Linux, macOS and Windows. Performance varies between native libraries (JNR, JNA) and reflection. Deterministic bootstrap order avoids surprises.
+The Posix façade must run on Linux, macOS and Windows.
+Performance varies between native libraries (JNR, JNA) and reflection.
+Deterministic bootstrap order avoids surprises.
*Decision*::
At first successful class-load we try providers in this strict order:
@@ -62,7 +64,8 @@ Override with `-Dchronicle.posix.provider`.
== ALL-DOC-002 Real-Time Documentation Loop
*Context*::
-Documentation drift led to mis-generated code from AI agents. The team adopted a “doc-first or doc-alongside” workflow but lacked formal guidance.
+Documentation drift led to mis-generated code from AI agents.
+The team adopted a “doc-first or doc-alongside” workflow but lacked formal guidance.
*Decision*::
* Update AsciiDoc **in the same commit** as code/tests.
@@ -78,27 +81,22 @@ Quicker feedback, cleaner PR reviews, slightly longer dev cycle per change.
Some environments (Graal native-image, security-restricted CI) cannot load native libs.
*Decision*::
-`NoOpPosixAPI` compiles everywhere, returns **0** for safe no-ops, **-1** (or
-throws `PosixRuntimeException(errno=ENOSYS)`) where silent failure may lose data.
+`NoOpPosixAPI` compiles everywhere, returns **0** for safe no-ops, **-1** (or throws `PosixRuntimeException(errno=ENOSYS)`) where silent failure may lose data.
== POSIX-NF-O-006 Affinity Mask Sizing Policy
*Context*::
-Regression POSIX-2025-05 observed incorrect CPU pinning on hosts with more than
-thirty-two logical processors. Bit masks were truncated because offsets used
-byte indices rather than `int` words and mask buffers were only sized for a
-single `long`.
+Regression POSIX-2025-05 observed incorrect CPU pinning on hosts with more than thirty-two logical processors.
+Bit masks were truncated because offsets used byte indices rather than `int` words and mask buffers were only sized for a single `long`.
*Decision*::
* Introduce `CpuSetUtil` to normalise mask sizing (round up to whole
- `Long.BYTES` blocks) and byte packing.
-* Apply the helper in `PosixJNAAffinity` and export the same contract to
- embedding projects (Java Thread Affinity).
+`Long.BYTES` blocks) and byte packing.
+* Apply the helper in `PosixJNAAffinity` and export the same contract to embedding projects (Java Thread Affinity).
* Add pure-Java tests that simulate > 64-core schedulers.
*Alternatives*::
-*Hard-code buffer sizes per architecture* – rejected because it risks further
-drift when porting to new platforms.
+*Hard-code buffer sizes per architecture* – rejected because it risks further drift when porting to new platforms.
*Consequences*::
* Mask handling now covers arbitrary CPU counts permitted by the kernel.
diff --git a/src/main/adoc/project-requirements.adoc b/src/main/adoc/project-requirements.adoc
index 2c492b6..8020849 100644
--- a/src/main/adoc/project-requirements.adoc
+++ b/src/main/adoc/project-requirements.adoc
@@ -7,10 +7,8 @@
== Introduction
-_OpenHFT Posix_ is a low-level Java façade over selected POSIX / Linux
-system calls, aimed at deterministic, zero-GC latency paths in trading
-workloads. It runs on Linux, macOS, Windows and in sandboxed CI by
-degrading to a *No-Op* implementation.
+_OpenHFT Posix_ is a low-level Java façade over selected POSIX / Linux system calls, aimed at deterministic, zero-GC latency paths in trading workloads.
+It runs on Linux, macOS, Windows and in sandboxed CI by degrading to a *No-Op* implementation.
== POSIX-FN-Requirements
diff --git a/src/main/config/spotbugs-exclude.xml b/src/main/config/spotbugs-exclude.xml
index 0e97348..2914527 100644
--- a/src/main/config/spotbugs-exclude.xml
+++ b/src/main/config/spotbugs-exclude.xml
@@ -1,38 +1,44 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- POSIX-SEC-204: ProcessBuilder is invoked with fixed argv and never shells out; arguments are passed as execve tokens so there is no injection surface.
-
-
-
-
-
-
- POSIX-OPS-102: Reads the kernel-managed /proc/<pid>/maps file for diagnostics; caller supplies PID but resource remains confined to procfs.
-
-
-
-
-
-
- POSIX-API-117: Methods wrap errno reporting, throwing RuntimeException to preserve existing API contracts; change to checked exceptions would be breaking.
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POSIX-SEC-204: ProcessBuilder is invoked with fixed argv and never shells out; arguments are passed
+ as execve tokens so there is no injection surface.
+
+
+
+
+
+
+
+ POSIX-OPS-102: Reads the kernel-managed /proc/<pid>/maps file for diagnostics; caller
+ supplies PID but resource remains confined to procfs.
+
+
+
+
+
+
+
+ POSIX-API-117: Methods wrap errno reporting, throwing RuntimeException to preserve existing API
+ contracts; change to checked exceptions would be breaking.
+
+
+
diff --git a/src/main/java/net/openhft/posix/ClockId.java b/src/main/java/net/openhft/posix/ClockId.java
index 27450c6..fb64a83 100644
--- a/src/main/java/net/openhft/posix/ClockId.java
+++ b/src/main/java/net/openhft/posix/ClockId.java
@@ -8,41 +8,65 @@
* @see clock_gettime(2)
*/
public enum ClockId {
- /** The system-wide real-time clock. */
+ /**
+ * The system-wide real-time clock.
+ */
CLOCK_REALTIME(0),
- /** Monotonic clock that cannot be set. */
+ /**
+ * Monotonic clock that cannot be set.
+ */
CLOCK_MONOTONIC(1),
- /** CPU time consumed by the process. */
+ /**
+ * CPU time consumed by the process.
+ */
CLOCK_PROCESS_CPUTIME_ID(2),
- /** CPU time consumed by the thread. */
+ /**
+ * CPU time consumed by the thread.
+ */
CLOCK_THREAD_CPUTIME_ID(3),
- /** Monotonic clock without NTP adjustments. */
+ /**
+ * Monotonic clock without NTP adjustments.
+ */
CLOCK_MONOTONIC_RAW(4),
- /** Faster but coarse real-time clock. */
+ /**
+ * Faster but coarse real-time clock.
+ */
CLOCK_REALTIME_COARSE(5),
- /** Faster but coarse monotonic clock. */
+ /**
+ * Faster but coarse monotonic clock.
+ */
CLOCK_MONOTONIC_COARSE(6),
- /** Monotonic clock including suspend time. */
+ /**
+ * Monotonic clock including suspend time.
+ */
CLOCK_BOOTTIME(7),
- /** Real-time clock used for alarms. */
+ /**
+ * Real-time clock used for alarms.
+ */
CLOCK_REALTIME_ALARM(8),
- /** Boot-time clock used for alarms. */
+ /**
+ * Boot-time clock used for alarms.
+ */
CLOCK_BOOTTIME_ALARM(9),
- /** SGI cycle counter. */
+ /**
+ * SGI cycle counter.
+ */
CLOCK_SGI_CYCLE(10);
- /** Native constant value. */
+ /**
+ * Native constant value.
+ */
private final int value;
/**
diff --git a/src/main/java/net/openhft/posix/LockfFlag.java b/src/main/java/net/openhft/posix/LockfFlag.java
index 210dfe3..278ca77 100644
--- a/src/main/java/net/openhft/posix/LockfFlag.java
+++ b/src/main/java/net/openhft/posix/LockfFlag.java
@@ -8,19 +8,29 @@
* @see lockf(3)
*/
public enum LockfFlag {
- /** Release the specified section. */
+ /**
+ * Release the specified section.
+ */
F_ULOCK(0),
- /** Exclusive lock on the section. Blocks until available. */
+ /**
+ * Exclusive lock on the section. Blocks until available.
+ */
F_LOCK(1),
- /** Non-blocking exclusive lock. */
+ /**
+ * Non-blocking exclusive lock.
+ */
F_TLOCK(2),
- /** Test whether a lock is held by another process. */
+ /**
+ * Test whether a lock is held by another process.
+ */
F_TEST(3);
- /** Native constant value. */
+ /**
+ * Native constant value.
+ */
final int value;
/**
diff --git a/src/main/java/net/openhft/posix/MAdviseFlag.java b/src/main/java/net/openhft/posix/MAdviseFlag.java
index def3be8..0799827 100644
--- a/src/main/java/net/openhft/posix/MAdviseFlag.java
+++ b/src/main/java/net/openhft/posix/MAdviseFlag.java
@@ -8,58 +8,94 @@
* @see madvise(2)
*/
public enum MAdviseFlag {
- /** No further special treatment. */
+ /**
+ * No further special treatment.
+ */
MADV_NORMAL(0),
- /** Expect random page references. */
+ /**
+ * Expect random page references.
+ */
MADV_RANDOM(1),
- /** Expect sequential page references. */
+ /**
+ * Expect sequential page references.
+ */
MADV_SEQUENTIAL(2),
- /** Will need these pages. */
+ /**
+ * Will need these pages.
+ */
MADV_WILLNEED(3),
- /** No need for these pages. */
+ /**
+ * No need for these pages.
+ */
MADV_DONTNEED(4),
- /** Free pages only if memory pressure. */
+ /**
+ * Free pages only if memory pressure.
+ */
MADV_FREE(8),
- /** Remove these pages and resources. */
+ /**
+ * Remove these pages and resources.
+ */
MADV_REMOVE(9),
- /** Do not inherit across fork. */
+ /**
+ * Do not inherit across fork.
+ */
MADV_DONTFORK(10),
- /** Inherit across fork. */
+ /**
+ * Inherit across fork.
+ */
MADV_DOFORK(11),
- /** KSM may merge identical pages. */
+ /**
+ * KSM may merge identical pages.
+ */
MADV_MERGEABLE(12),
- /** KSM may not merge identical pages. */
+ /**
+ * KSM may not merge identical pages.
+ */
MADV_UNMERGEABLE(13),
- /** Hint backing with huge pages. */
+ /**
+ * Hint backing with huge pages.
+ */
MADV_HUGEPAGE(14),
- /** Hint not worth backing with huge pages. */
+ /**
+ * Hint not worth backing with huge pages.
+ */
MADV_NOHUGEPAGE(15),
- /** Exclude from core dump and override the coredump filter. */
+ /**
+ * Exclude from core dump and override the coredump filter.
+ */
MADV_DONTDUMP(16),
- /** Clear the MADV_DONTDUMP flag. */
+ /**
+ * Clear the MADV_DONTDUMP flag.
+ */
MADV_DODUMP(17),
- /** Zero memory on fork in the child. */
+ /**
+ * Zero memory on fork in the child.
+ */
MADV_WIPEONFORK(18),
- /** Undo MADV_WIPEONFORK. */
+ /**
+ * Undo MADV_WIPEONFORK.
+ */
MADV_KEEPONFORK(19);
- /** Native constant value. */
+ /**
+ * Native constant value.
+ */
final int value;
/**
diff --git a/src/main/java/net/openhft/posix/MMapFlag.java b/src/main/java/net/openhft/posix/MMapFlag.java
index 589de22..0517b95 100644
--- a/src/main/java/net/openhft/posix/MMapFlag.java
+++ b/src/main/java/net/openhft/posix/MMapFlag.java
@@ -8,13 +8,19 @@
* @see mmap(2)
*/
public enum MMapFlag {
- /** Memory mapping to be shared with other processes. */
+ /**
+ * Memory mapping to be shared with other processes.
+ */
SHARED(1),
- /** Memory mapping to be private to the process. */
+ /**
+ * Memory mapping to be private to the process.
+ */
PRIVATE(2);
- /** Native constant value. */
+ /**
+ * Native constant value.
+ */
private final int value;
/**
diff --git a/src/main/java/net/openhft/posix/MMapProt.java b/src/main/java/net/openhft/posix/MMapProt.java
index e7eb3e3..918f9bb 100644
--- a/src/main/java/net/openhft/posix/MMapProt.java
+++ b/src/main/java/net/openhft/posix/MMapProt.java
@@ -8,28 +8,42 @@
* @see mmap(2)
*/
public enum MMapProt {
- /** Allow read access. */
+ /**
+ * Allow read access.
+ */
PROT_READ(1),
- /** Allow write access. */
+ /**
+ * Allow write access.
+ */
PROT_WRITE(2),
- /** Allow both read and write access. */
+ /**
+ * Allow both read and write access.
+ */
PROT_READ_WRITE(3),
- /** Allow execute access. */
+ /**
+ * Allow execute access.
+ */
PROT_EXEC(4),
- /** Allow execute and read access. */
+ /**
+ * Allow execute and read access.
+ */
PROT_EXEC_READ(5),
- /** No access allowed. */
+ /**
+ * No access allowed.
+ */
PROT_NONE(8);
- /** Native constant value. */
+ /**
+ * Native constant value.
+ */
final int value;
- /**
+ /**
* @param value native constant value
*/
MMapProt(int value) {
diff --git a/src/main/java/net/openhft/posix/MSyncFlag.java b/src/main/java/net/openhft/posix/MSyncFlag.java
index 16b6713..2b38536 100644
--- a/src/main/java/net/openhft/posix/MSyncFlag.java
+++ b/src/main/java/net/openhft/posix/MSyncFlag.java
@@ -8,16 +8,24 @@
* @see msync(2)
*/
public enum MSyncFlag {
- /** Sync memory asynchronously. */
+ /**
+ * Sync memory asynchronously.
+ */
MS_ASYNC(1),
- /** Invalidate caches. */
+ /**
+ * Invalidate caches.
+ */
MS_INVALIDATE(2),
- /** Synchronous memory sync. */
+ /**
+ * Synchronous memory sync.
+ */
MS_SYNC(4);
- /** Native constant value. */
+ /**
+ * Native constant value.
+ */
private final int value;
/**
diff --git a/src/main/java/net/openhft/posix/Mapping.java b/src/main/java/net/openhft/posix/Mapping.java
index 59540d2..ccb7e47 100644
--- a/src/main/java/net/openhft/posix/Mapping.java
+++ b/src/main/java/net/openhft/posix/Mapping.java
@@ -11,6 +11,7 @@
*
* On a 32-bit VM the addresses are truncated.
* Instances are thread-safe and immutable.
+ *
* @see proc(5)
*/
public final class Mapping {
@@ -42,8 +43,8 @@ public final class Mapping {
* Parses a mapping line from {@code /proc/[pid]/maps}.
*
* @param line textual line from the maps file
- * @throws NumberFormatException if address or inode fields are not
- * valid hex or decimal numbers
+ * @throws NumberFormatException if address or inode fields are not
+ * valid hex or decimal numbers
* @throws ArrayIndexOutOfBoundsException if fields are missing
*/
public Mapping(String line) {
@@ -60,42 +61,58 @@ public Mapping(String line) {
toString = line;
}
- /** Start address of the mapping. */
+ /**
+ * Start address of the mapping.
+ */
public long addr() {
return addr;
}
- /** Length of the mapping. */
+ /**
+ * Length of the mapping.
+ */
public long length() {
return length;
}
- /** Offset into the file or VM object. */
+ /**
+ * Offset into the file or VM object.
+ */
public long offset() {
return offset;
}
- /** Inode number. */
+ /**
+ * Inode number.
+ */
public long inode() {
return inode;
}
- /** Permission string such as {@code r-xp}. */
+ /**
+ * Permission string such as {@code r-xp}.
+ */
public String perms() {
return perms;
}
- /** Device in {@code major:minor} form. */
+ /**
+ * Device in {@code major:minor} form.
+ */
public String device() {
return device;
}
- /** File path of the mapping if any. */
+ /**
+ * File path of the mapping if any.
+ */
public String path() {
return path;
}
- /** Original line from the maps file. */
+ /**
+ * Original line from the maps file.
+ */
@Override
public String toString() {
return toString;
diff --git a/src/main/java/net/openhft/posix/MclFlag.java b/src/main/java/net/openhft/posix/MclFlag.java
index d11fa17..a5f4060 100644
--- a/src/main/java/net/openhft/posix/MclFlag.java
+++ b/src/main/java/net/openhft/posix/MclFlag.java
@@ -8,19 +8,29 @@
* @see mlockall(2)
*/
public enum MclFlag {
- /** Lock all current pages in memory. */
+ /**
+ * Lock all current pages in memory.
+ */
MclCurrent(1),
- /** Lock all future pages in memory. */
+ /**
+ * Lock all future pages in memory.
+ */
MclFuture(2),
- /** Lock all current pages in memory on fault. */
+ /**
+ * Lock all current pages in memory on fault.
+ */
MclCurrentOnFault(1 + 4),
- /** Lock all future pages in memory on fault. */
+ /**
+ * Lock all future pages in memory on fault.
+ */
MclFutureOnFault(2 + 4);
- /** Native constant value. */
+ /**
+ * Native constant value.
+ */
private final int code;
/**
diff --git a/src/main/java/net/openhft/posix/OpenFlag.java b/src/main/java/net/openhft/posix/OpenFlag.java
index 255e6b8..34c51b1 100644
--- a/src/main/java/net/openhft/posix/OpenFlag.java
+++ b/src/main/java/net/openhft/posix/OpenFlag.java
@@ -8,43 +8,69 @@
* @see open(2)
*/
public enum OpenFlag {
- /** Open for reading only. */
+ /**
+ * Open for reading only.
+ */
O_RDONLY(0x0000),
- /** Open for writing only. */
+ /**
+ * Open for writing only.
+ */
O_WRONLY(0x0001),
- /** Open for reading and writing. */
+ /**
+ * Open for reading and writing.
+ */
O_RDWR(0x0002),
- /** Non-blocking mode. */
+ /**
+ * Non-blocking mode.
+ */
O_NONBLOCK(0x0004),
- /** Append mode. */
+ /**
+ * Append mode.
+ */
O_APPEND(0x0008),
- /** Open with shared file lock. */
+ /**
+ * Open with shared file lock.
+ */
O_SHLOCK(0x0010),
- /** Open with exclusive file lock. */
+ /**
+ * Open with exclusive file lock.
+ */
O_EXLOCK(0x0020),
- /** Signal process group when data is ready. */
+ /**
+ * Signal process group when data is ready.
+ */
O_ASYNC(0x0040),
- /** Synchronous writes. */
+ /**
+ * Synchronous writes.
+ */
O_FSYNC(0x0080),
- /** Create if non-existent. */
+ /**
+ * Create if non-existent.
+ */
O_CREAT(0x0200),
- /** Truncate to zero length. */
+ /**
+ * Truncate to zero length.
+ */
O_TRUNC(0x0400),
- /** Error if already exists. */
+ /**
+ * Error if already exists.
+ */
O_EXCL(0x0800);
- /** Native constant value. */
+ /**
+ * Native constant value.
+ */
final int value;
/**
diff --git a/src/main/java/net/openhft/posix/PosixRuntimeException.java b/src/main/java/net/openhft/posix/PosixRuntimeException.java
index 15d8307..41e90b2 100644
--- a/src/main/java/net/openhft/posix/PosixRuntimeException.java
+++ b/src/main/java/net/openhft/posix/PosixRuntimeException.java
@@ -5,10 +5,14 @@
* produced by the underlying native call.
*/
public class PosixRuntimeException extends RuntimeException {
- /** Used to maintain serialization compatibility. */
+ /**
+ * Used to maintain serialization compatibility.
+ */
private static final long serialVersionUID = 0L;
- /** POSIX errno captured from the failing call. */
+ /**
+ * POSIX errno captured from the failing call.
+ */
private final int errno;
/**
diff --git a/src/main/java/net/openhft/posix/ProcMaps.java b/src/main/java/net/openhft/posix/ProcMaps.java
index 15b16f9..473865e 100644
--- a/src/main/java/net/openhft/posix/ProcMaps.java
+++ b/src/main/java/net/openhft/posix/ProcMaps.java
@@ -3,10 +3,10 @@
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
diff --git a/src/main/java/net/openhft/posix/WhenceFlag.java b/src/main/java/net/openhft/posix/WhenceFlag.java
index 9ade1d2..794e12a 100644
--- a/src/main/java/net/openhft/posix/WhenceFlag.java
+++ b/src/main/java/net/openhft/posix/WhenceFlag.java
@@ -8,19 +8,29 @@
* @see lseek(2)
*/
public enum WhenceFlag {
- /** Offset is set to {@code offset} bytes. */
+ /**
+ * Offset is set to {@code offset} bytes.
+ */
SEEK_SET(1),
- /** Add {@code offset} to the current location. */
+ /**
+ * Add {@code offset} to the current location.
+ */
SEEK_CUR(2),
- /** Set offset relative to end of file. */
+ /**
+ * Set offset relative to end of file.
+ */
SEEK_END(3),
- /** Move to the next data region at or after {@code offset}. */
+ /**
+ * Move to the next data region at or after {@code offset}.
+ */
SEEK_DATA(4),
- /** Move to the next hole at or after {@code offset}. */
+ /**
+ * Move to the next hole at or after {@code offset}.
+ */
SEEK_HOLE(5);
// The integer value representing the whence flag
diff --git a/src/main/java/net/openhft/posix/internal/core/Jvm.java b/src/main/java/net/openhft/posix/internal/core/Jvm.java
index e25b6bd..3ba8d2f 100644
--- a/src/main/java/net/openhft/posix/internal/core/Jvm.java
+++ b/src/main/java/net/openhft/posix/internal/core/Jvm.java
@@ -8,7 +8,9 @@
*/
public final class Jvm {
- /** Private constructor to prevent instantiation. */
+ /**
+ * Private constructor to prevent instantiation.
+ */
private Jvm() {
}
diff --git a/src/main/java/net/openhft/posix/internal/core/OS.java b/src/main/java/net/openhft/posix/internal/core/OS.java
index 12cb3ba..57a861c 100644
--- a/src/main/java/net/openhft/posix/internal/core/OS.java
+++ b/src/main/java/net/openhft/posix/internal/core/OS.java
@@ -12,7 +12,9 @@ public final class OS {
*/
public static final String OS_NAME = System.getProperty("os.name", "?");
- /** Private constructor to prevent instantiation. */
+ /**
+ * Private constructor to prevent instantiation.
+ */
private OS() {
}
diff --git a/src/main/java/net/openhft/posix/internal/jnr/JNRPosixAPI.java b/src/main/java/net/openhft/posix/internal/jnr/JNRPosixAPI.java
index 77c4a95..358da76 100644
--- a/src/main/java/net/openhft/posix/internal/jnr/JNRPosixAPI.java
+++ b/src/main/java/net/openhft/posix/internal/jnr/JNRPosixAPI.java
@@ -1,11 +1,11 @@
package net.openhft.posix.internal.jnr;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jnr.constants.platform.Errno;
import jnr.ffi.Platform;
import jnr.ffi.Pointer;
import jnr.ffi.Runtime;
import jnr.ffi.provider.FFIProvider;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.openhft.posix.*;
import net.openhft.posix.internal.UnsafeMemory;
import net.openhft.posix.internal.core.Jvm;
@@ -143,8 +143,8 @@ public long mmap(long addr, long length, int prot, int flags, int fd, long offse
/**
* Performs the mlock2 system call.
*
- * @param addr The address to lock.
- * @param length The length of the memory to lock.
+ * @param addr The address to lock.
+ * @param length The length of the memory to lock.
* @param lockOnFault Whether to lock on fault.
* @return The result of the mlock2 system call.
*/
@@ -160,7 +160,7 @@ private int mlock2_(long addr, long length, boolean lockOnFault) {
@Override
public boolean mlock(long addr, long length) {
- if(Jvm.isAzul()) {
+ if (Jvm.isAzul()) {
LOGGER.warn("mlock called but ignored for Azul");
return true; // no-op on Azul, ignore
}
@@ -175,7 +175,7 @@ public boolean mlock(long addr, long length) {
@Override
public boolean mlock2(long addr, long length, boolean lockOnFault) {
- if(Jvm.isAzul()) {
+ if (Jvm.isAzul()) {
LOGGER.warn("mlock2 called but ignored for Azul");
return true; // no-op on Azul, ignore
}
@@ -291,17 +291,17 @@ public int fallocate(int fd, int mode, long offset, long length) {
if (ret == 0)
return ret;
} catch (Throwable e) {
- if(mode != 0)
+ if (mode != 0)
throw e;
}
// if both fallocate attempts fail, then revert to posix_ftruncate when mode = 0
// NB: this use case uses cooperative locking to help close a small race window
- if(mode == 0) {
- try(FileLocker lock = new FileLocker(fd)) {
+ if (mode == 0) {
+ try (FileLocker lock = new FileLocker(fd)) {
lock.ensureAcquired();
int ret = jnr.posix_fallocate(fd, offset, length);
- if(ret == 0)
+ if (ret == 0)
return ret;
} catch (Throwable ignored) {
}
diff --git a/src/main/java/net/openhft/posix/internal/jnr/JNRPosixInterface.java b/src/main/java/net/openhft/posix/internal/jnr/JNRPosixInterface.java
index f70a71f..39edc63 100644
--- a/src/main/java/net/openhft/posix/internal/jnr/JNRPosixInterface.java
+++ b/src/main/java/net/openhft/posix/internal/jnr/JNRPosixInterface.java
@@ -16,12 +16,15 @@ public interface JNRPosixInterface {
long lseek(int fd, long offset, int whence);
int lockf(int fd, int cmd, long len);
+
int flock(int fd, int operation);
int ftruncate(int fd, long offset);
int fallocate(int fd, int mode, long offset, long length);
+
int fallocate64(int fd, int mode, long offset, long length);
+
int posix_fallocate(int fd, long offset, long length);
int close(int fd);
@@ -57,6 +60,7 @@ public interface JNRPosixInterface {
int clock_gettime(int clockId, long ptr);
int mlock(long addr, long length);
+
int mlock2(long addr, long length, int flags);
int mlockall(int flags);
diff --git a/src/main/java/net/openhft/posix/internal/package-info.java b/src/main/java/net/openhft/posix/internal/package-info.java
index 8f064f4..5b8c3a1 100644
--- a/src/main/java/net/openhft/posix/internal/package-info.java
+++ b/src/main/java/net/openhft/posix/internal/package-info.java
@@ -2,8 +2,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/main/java/net/openhft/posix/package-info.java b/src/main/java/net/openhft/posix/package-info.java
index 4a3e216..914f0c8 100644
--- a/src/main/java/net/openhft/posix/package-info.java
+++ b/src/main/java/net/openhft/posix/package-info.java
@@ -9,14 +9,14 @@
* long ptr = posix.malloc(128);
* posix.free(ptr);
*
- *
+ *
* File example:
*
* PosixAPI posix = PosixAPI.posix();
* int fd = posix.open("/tmp/data", OpenFlag.O_CREAT, 0644);
* posix.close(fd);
*
- *
+ *
* All helpers strive for zero heap allocation.
*/
package net.openhft.posix;
diff --git a/src/test/java/net/openhft/posix/internal/PosixAPIHolderTest.java b/src/test/java/net/openhft/posix/internal/PosixAPIHolderTest.java
index 80d3f43..78de337 100644
--- a/src/test/java/net/openhft/posix/internal/PosixAPIHolderTest.java
+++ b/src/test/java/net/openhft/posix/internal/PosixAPIHolderTest.java
@@ -15,7 +15,8 @@
import java.util.Map;
import static net.openhft.posix.internal.UnsafeMemory.UNSAFE;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
/**
* Tests around {@link PosixAPIHolder} to ensure the documented provider order
diff --git a/src/test/java/net/openhft/posix/internal/jna/JNAPosixAPITest.java b/src/test/java/net/openhft/posix/internal/jna/JNAPosixAPITest.java
index e35abbd..b38f716 100644
--- a/src/test/java/net/openhft/posix/internal/jna/JNAPosixAPITest.java
+++ b/src/test/java/net/openhft/posix/internal/jna/JNAPosixAPITest.java
@@ -1,16 +1,15 @@
package net.openhft.posix.internal.jna;
import com.sun.jna.Pointer;
-import org.junit.Test;
+import net.openhft.posix.internal.ReflectionAccess;
import org.junit.Assume;
+import org.junit.Test;
import java.lang.reflect.Field;
import java.util.ArrayList;
-import net.openhft.posix.internal.ReflectionAccess;
-
import static net.openhft.posix.internal.UnsafeMemory.UNSAFE;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
/**
* Smoke tests for the JNA-backed provider to ensure constructor wiring and the
diff --git a/src/test/java/net/openhft/posix/internal/jnr/JNRPosixAPINonNativeTest.java b/src/test/java/net/openhft/posix/internal/jnr/JNRPosixAPINonNativeTest.java
index 2e6bc14..f89121b 100644
--- a/src/test/java/net/openhft/posix/internal/jnr/JNRPosixAPINonNativeTest.java
+++ b/src/test/java/net/openhft/posix/internal/jnr/JNRPosixAPINonNativeTest.java
@@ -17,11 +17,7 @@
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Deque;
-import java.util.List;
+import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntSupplier;
diff --git a/src/test/java/net/openhft/posix/internal/jnr/JNRPosixAPITest.java b/src/test/java/net/openhft/posix/internal/jnr/JNRPosixAPITest.java
index ca1f52e..6113cf7 100644
--- a/src/test/java/net/openhft/posix/internal/jnr/JNRPosixAPITest.java
+++ b/src/test/java/net/openhft/posix/internal/jnr/JNRPosixAPITest.java
@@ -167,6 +167,7 @@ public void get_nprocs() {
/**
* Applies the given int supplier N times over N threads, adding each result to a set
+ *
* @return - the size of the set
*/
int poolIntReduce(int N, Supplier r) throws InterruptedException {
diff --git a/src/test/java/net/openhft/posix/internal/raw/RawPosixAPITest.java b/src/test/java/net/openhft/posix/internal/raw/RawPosixAPITest.java
index 466fd28..9dcc427 100644
--- a/src/test/java/net/openhft/posix/internal/raw/RawPosixAPITest.java
+++ b/src/test/java/net/openhft/posix/internal/raw/RawPosixAPITest.java
@@ -3,7 +3,7 @@
import net.openhft.posix.MclFlag;
import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertFalse;
/**
* Ensures {@link RawPosixAPI} can be subclassed without extra wiring and that
From 090cb62ca4a06cc0b647a1c1db4f69eb101a889d Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Tue, 28 Oct 2025 11:00:56 +0000
Subject: [PATCH 12/17] Refine AsciiDoc formatting for consistency and clarity
---
README.adoc | 12 +++----
src/main/adoc/decision-log.adoc | 56 ++++++++++++++++-----------------
2 files changed, 34 insertions(+), 34 deletions(-)
diff --git a/README.adoc b/README.adoc
index aeeeae5..5b27854 100644
--- a/README.adoc
+++ b/README.adoc
@@ -8,7 +8,7 @@ Peter Lawrey, 31/08/2021
:source-highlighter: rouge
[abstract]
-The _OpenHFT Posix_ module is a zero-GC, low-latency Java façade over a **portable subset of POSIX/Linux system calls**, with provider fall-back to JNR, JNA or raw/reflective variants.
+The _OpenHFT Posix_ module is a zero-GC, low-latency Java façade over a *portable subset of POSIX/Linux system calls*, with provider fall-back to JNR, JNA or raw/reflective variants.
Chronicle components rely on it for deterministic file-IO, memory-mapping and CPU-affinity operations that the JDK does not expose.
Audience: Internal, Stability: Stable
@@ -51,14 +51,14 @@ posix.munmap(addr, 4096);
|===
| Provider | Native layer | Typical when…
-| `JNRPosixAPI` | JNR-FFI | **Linux** & x86_64/ARM, fastest syscalls
-| `WinJNRPosixAPI` | JNR-FFI | **Windows** equivalents (subset)
+| `JNRPosixAPI` | JNR-FFI | *Linux* & x86_64/ARM, fastest syscalls
+| `WinJNRPosixAPI` | JNR-FFI | *Windows* equivalents (subset)
| `JNAPosixAPI` | JNA | exotic/legacy platforms
| `RawPosixAPI` | Reflection | JVM ≥ 21 with `--add-opens`
| `NoOpPosixAPI` | — | CI sandboxes / Graal native-image
|===
-`-Dchronicle.posix.provider=` *provider-name* overrides the auto-choice.
+`-Dchronicle.posix.provider=` _provider-name_ overrides the auto-choice.
== Supported system calls
@@ -75,7 +75,7 @@ posix.munmap(addr, 4096);
| addr / 0 | `MAP_FAILED` → -1 sentinel
| CPU-affinity
-| `sched_*affinity*`, helpers `sched_setaffinity_as` …
+| `sched__affinity_`, helpers `sched_setaffinity_as` …
| 0 | portable bit-mask helpers
| Timing
@@ -93,7 +93,7 @@ mvn -q verify
Environment variables:
-* `POSIX_TEST_ALLOW_NATIVE` – set to *false* in CI to force `NoOpPosixAPI`.
+* `POSIX_TEST_ALLOW_NATIVE` – set to _false_ in CI to force `NoOpPosixAPI`.
* `POSIX_SYSLOG_LEVEL` – adjust logging noise during native provider load.
== Troubleshooting
diff --git a/src/main/adoc/decision-log.adoc b/src/main/adoc/decision-log.adoc
index d491311..3343202 100644
--- a/src/main/adoc/decision-log.adoc
+++ b/src/main/adoc/decision-log.adoc
@@ -19,30 +19,30 @@
== ALL-DOC-001 Language & Character-Set Policy
-*Context*::
+_Context_::
Chronicle teams span multiple regions.
Mixed US/UK spelling and occasional UTF-8 artefacts caused build failures in binary wire-format tests and confused spell-checkers.
-*Decision*::
-* Use **British English** spelling for prose/code except fixed US technical spellings (`synchronized`, `Serializable`).
-* Restrict all source and documentation files to **ASCII-7** code-points (0-127).
+_Decision_::
+* Use *British English* spelling for prose/code except fixed US technical spellings (`synchronized`, `Serializable`).
+* Restrict all source and documentation files to *ASCII-7* code-points (0-127).
-*Alternatives considered*::
-*Allow UTF-8 universally* – rejected: breaks zero-copy buffers that rely on MSB=0.
-*Allow US spellings* – rejected: inconsistent with London HQ style guide.
+_Alternatives considered_::
+_Allow UTF-8 universally_ – rejected: breaks zero-copy buffers that rely on MSB=0.
+_Allow US spellings_ – rejected: inconsistent with London HQ style guide.
-*Consequences*::
+_Consequences_::
* Tooling: CI linter added (`scripts/check-ascii.sh`).
* On-boarding: documented in `AGENTS.md`; IDE templates updated.
== POSIX-FN-002 Provider Fallback Order
-*Context*::
+_Context_::
The Posix façade must run on Linux, macOS and Windows.
Performance varies between native libraries (JNR, JNA) and reflection.
Deterministic bootstrap order avoids surprises.
-*Decision*::
+_Decision_::
At first successful class-load we try providers in this strict order:
. `JNRPosixAPI`
@@ -53,55 +53,55 @@ At first successful class-load we try providers in this strict order:
Override with `-Dchronicle.posix.provider`.
-*Alternatives*::
-*ServiceLoader scan* – too slow; non-deterministic.
-*OS-hard-coded providers* – would fork the code-base per platform.
+_Alternatives_::
+_ServiceLoader scan_ – too slow; non-deterministic.
+_OS-hard-coded providers_ – would fork the code-base per platform.
-*Consequences*::
+_Consequences_::
* Single-point selection in `PosixAPIHolder`.
* Unit tests must set `POSIX_TEST_ALLOW_NATIVE=false` to force No-Op in CI.
== ALL-DOC-002 Real-Time Documentation Loop
-*Context*::
+_Context_::
Documentation drift led to mis-generated code from AI agents.
The team adopted a “doc-first or doc-alongside” workflow but lacked formal guidance.
-*Decision*::
-* Update AsciiDoc **in the same commit** as code/tests.
+_Decision_::
+* Update AsciiDoc *in the same commit* as code/tests.
* CI fails if a PR touches `src/main/java` without touching any `.adoc` _and_
`scripts/doc-drift-check.sh` flags potential drift.
-*Consequences*::
+_Consequences_::
Quicker feedback, cleaner PR reviews, slightly longer dev cycle per change.
== POSIX-NF-O-003 No-Op Provider Contract
-*Context*::
+_Context_::
Some environments (Graal native-image, security-restricted CI) cannot load native libs.
-*Decision*::
-`NoOpPosixAPI` compiles everywhere, returns **0** for safe no-ops, **-1** (or throws `PosixRuntimeException(errno=ENOSYS)`) where silent failure may lose data.
+_Decision_::
+`NoOpPosixAPI` compiles everywhere, returns *0* for safe no-ops, *-1* (or throws `PosixRuntimeException(errno=ENOSYS)`) where silent failure may lose data.
== POSIX-NF-O-006 Affinity Mask Sizing Policy
-*Context*::
+_Context_::
Regression POSIX-2025-05 observed incorrect CPU pinning on hosts with more than thirty-two logical processors.
Bit masks were truncated because offsets used byte indices rather than `int` words and mask buffers were only sized for a single `long`.
-*Decision*::
+_Decision_::
* Introduce `CpuSetUtil` to normalise mask sizing (round up to whole
`Long.BYTES` blocks) and byte packing.
* Apply the helper in `PosixJNAAffinity` and export the same contract to embedding projects (Java Thread Affinity).
* Add pure-Java tests that simulate > 64-core schedulers.
-*Alternatives*::
-*Hard-code buffer sizes per architecture* – rejected because it risks further drift when porting to new platforms.
+_Alternatives_::
+_Hard-code buffer sizes per architecture_ – rejected because it risks further drift when porting to new platforms.
-*Consequences*::
+_Consequences_::
* Mask handling now covers arbitrary CPU counts permitted by the kernel.
* Shared utility simplifies future native integrations.
-`lastError()` fixed at **0**.
+`lastError()` fixed at *0*.
-*Consequences*::
+_Consequences_::
Baseline behaviour predictable; integration tests must stub filesystem side-effects.
From ca5ea9a5319cca0faee17e5c5f0533d4e80ee758 Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Tue, 28 Oct 2025 11:00:56 +0000
Subject: [PATCH 13/17] Add non-native coverage tests and tighten code-review
gates
---
pom.xml | 4 ++--
.../net/openhft/posix/internal/jnr/WinJNRPosixAPITest.java | 5 +++--
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/pom.xml b/pom.xml
index 03643ce..3178d23 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,8 +18,8 @@
3.6.0
- 10.26.1
- 4.9.8.1
+ 8.45.1
+ 4.8.6.6
1.14.0
3.28.0
0.8.14
diff --git a/src/test/java/net/openhft/posix/internal/jnr/WinJNRPosixAPITest.java b/src/test/java/net/openhft/posix/internal/jnr/WinJNRPosixAPITest.java
index 5288613..f568c81 100644
--- a/src/test/java/net/openhft/posix/internal/jnr/WinJNRPosixAPITest.java
+++ b/src/test/java/net/openhft/posix/internal/jnr/WinJNRPosixAPITest.java
@@ -5,6 +5,7 @@
import java.lang.reflect.Field;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.*;
@@ -54,8 +55,8 @@ public void delegatesToNativeInterfaces() throws Exception {
assertEquals(Runtime.getRuntime().availableProcessors(), api.get_nprocs_conf());
assertEquals(api.get_nprocs_conf(), api.get_nprocs());
- assertEquals(List.of("open", "lseek", "read", "write", "close", "pid", "strerror"), win.calls);
- assertEquals(List.of("tid"), kernel.calls);
+ assertEquals(Arrays.asList("open", "lseek", "read", "write", "close", "pid", "strerror"), win.calls);
+ assertEquals(Arrays.asList("tid"), kernel.calls);
}
private static void setField(Object target, String fieldName, Object value) throws Exception {
From 7e78c578ac1ac4ce2bec522245ea93a8e5b210ea Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Tue, 28 Oct 2025 14:12:55 +0000
Subject: [PATCH 14/17] Update character set policy from ASCII-7 to ISO-8859-1
in documentation
---
AGENTS.md | 6 +++---
src/main/adoc/decision-log.adoc | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index 620cde4..7c69218 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -11,8 +11,8 @@ LLM-based agents can accelerate development only if they respect our house rules
| 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. |
+| **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
@@ -92,7 +92,7 @@ This tight loop informs the AI accurately and creates immediate clarity for all
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.
+ 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
diff --git a/src/main/adoc/decision-log.adoc b/src/main/adoc/decision-log.adoc
index 3343202..9a6ba60 100644
--- a/src/main/adoc/decision-log.adoc
+++ b/src/main/adoc/decision-log.adoc
@@ -25,7 +25,7 @@ Mixed US/UK spelling and occasional UTF-8 artefacts caused build failures in bin
_Decision_::
* Use *British English* spelling for prose/code except fixed US technical spellings (`synchronized`, `Serializable`).
-* Restrict all source and documentation files to *ASCII-7* code-points (0-127).
+* Restrict all source and documentation files to *ISO-8859-1* code-points (0-255).
_Alternatives considered_::
_Allow UTF-8 universally_ – rejected: breaks zero-copy buffers that rely on MSB=0.
From 1f4c2bc7caf6a0271edac1881de76a1fda726f5c Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Wed, 29 Oct 2025 13:33:09 +0000
Subject: [PATCH 15/17] Refactor SpotBugs exclusions and remove deprecated
@SuppressFBWarnings annotations
---
src/main/java/net/openhft/posix/PosixAPI.java | 3 ---
src/main/java/net/openhft/posix/ProcMaps.java | 3 ---
src/main/java/net/openhft/posix/internal/jnr/JNRPosixAPI.java | 3 ---
3 files changed, 9 deletions(-)
diff --git a/src/main/java/net/openhft/posix/PosixAPI.java b/src/main/java/net/openhft/posix/PosixAPI.java
index 1a31be4..fb4a126 100644
--- a/src/main/java/net/openhft/posix/PosixAPI.java
+++ b/src/main/java/net/openhft/posix/PosixAPI.java
@@ -1,6 +1,4 @@
package net.openhft.posix;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.openhft.posix.internal.PosixAPIHolder;
import net.openhft.posix.internal.UnsafeMemory;
@@ -262,7 +260,6 @@ default int open(CharSequence path, OpenFlag flags, int perm) {
* @return The disk usage in bytes.
* @throws IOException If an I/O error occurs.
*/
- @SuppressFBWarnings(value = "COMMAND_INJECTION", justification = "POSIX-SEC-204: ProcessBuilder uses fixed argv without shell expansion")
default long du(String filename) throws IOException {
ProcessBuilder pb = new ProcessBuilder("du", filename);
pb.redirectErrorStream(true);
diff --git a/src/main/java/net/openhft/posix/ProcMaps.java b/src/main/java/net/openhft/posix/ProcMaps.java
index 473865e..0b69d94 100644
--- a/src/main/java/net/openhft/posix/ProcMaps.java
+++ b/src/main/java/net/openhft/posix/ProcMaps.java
@@ -1,7 +1,5 @@
package net.openhft.posix;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
@@ -23,7 +21,6 @@
*
* @see proc(5)
*/
-@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "POSIX-OPS-102: reads kernel-managed /proc//maps entries only")
public final class ProcMaps {
// A list to hold the memory mappings
private final List mappingList = new ArrayList<>();
diff --git a/src/main/java/net/openhft/posix/internal/jnr/JNRPosixAPI.java b/src/main/java/net/openhft/posix/internal/jnr/JNRPosixAPI.java
index 358da76..ff7bb09 100644
--- a/src/main/java/net/openhft/posix/internal/jnr/JNRPosixAPI.java
+++ b/src/main/java/net/openhft/posix/internal/jnr/JNRPosixAPI.java
@@ -1,6 +1,4 @@
package net.openhft.posix.internal.jnr;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jnr.constants.platform.Errno;
import jnr.ffi.Platform;
import jnr.ffi.Pointer;
@@ -25,7 +23,6 @@
* hard-coded numbers chosen for common architectures. If the kernel does not
* recognise a number the call gracefully falls back to the available wrapper.
*/
-@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_RUNTIMEEXCEPTION", justification = "POSIX-API-117: propagate errno via RuntimeException to honour existing interface")
public final class JNRPosixAPI implements PosixAPI {
private static final Logger LOGGER = LoggerFactory.getLogger(JNRPosixAPI.class);
From 7ce19f3779c0ec3ad85e351388a64ab0187234ac Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Wed, 29 Oct 2025 15:47:33 +0000
Subject: [PATCH 16/17] Remove unused SpotBugs annotations dependency from
pom.xml
---
pom.xml | 6 ------
1 file changed, 6 deletions(-)
diff --git a/pom.xml b/pom.xml
index 3178d23..701c092 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,12 +45,6 @@
org.slf4j
slf4j-api
-
- com.github.spotbugs
- spotbugs-annotations
- 4.8.6
- provided
-
net.java.dev.jna
From ae4f5eb8cfdc894c23d50e0782ac04353fabbfac Mon Sep 17 00:00:00 2001
From: Peter Lawrey
Date: Thu, 30 Oct 2025 11:00:25 +0000
Subject: [PATCH 17/17] Move Checkstyle config under src/main/config
---
pom.xml | 2 +-
src/main/config/checkstyle.xml | 210 +++++++++++++++++++++++++++++++++
2 files changed, 211 insertions(+), 1 deletion(-)
create mode 100644 src/main/config/checkstyle.xml
diff --git a/pom.xml b/pom.xml
index 701c092..3ca485c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -165,7 +165,7 @@
- net/openhft/quality/checkstyle/checkstyle.xml
+ src/main/config/checkstyle.xml
true
true
warning
diff --git a/src/main/config/checkstyle.xml b/src/main/config/checkstyle.xml
new file mode 100644
index 0000000..844dd90
--- /dev/null
+++ b/src/main/config/checkstyle.xml
@@ -0,0 +1,210 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+