diff --git a/LICENSE b/LICENSE deleted file mode 100644 index a1993e4de..000000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2014-2018 Chronicle Software Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/LICENSE.adoc b/LICENSE.adoc new file mode 100644 index 000000000..f93a31eb3 --- /dev/null +++ b/LICENSE.adoc @@ -0,0 +1,13 @@ +== Copyright 2016-2025 chronicle.software + +Licensed under the *Apache License, Version 2.0* (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.adoc b/README.adoc index 3916ae8a4..13720fe77 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,11 @@ = Thread Affinity -image::/images/Thread-Affinity_line.png[width=20%] +image::docs/images/Thread-Affinity_line.png[width=20%] == Version [#image-maven] -[caption="", link=https://maven-badges.herokuapp.com/maven-central/net.openhft/affinity] +[caption="",link=https://maven-badges.herokuapp.com/maven-central/net.openhft/affinity] image::https://maven-badges.herokuapp.com/maven-central/net.openhft/affinity/badge.svg[] image:https://javadoc.io/badge2/net.openhft/affinity/javadoc.svg[link="https://www.javadoc.io/doc/net.openhft/affinity/latest/index.html"] @@ -14,9 +14,21 @@ Lets you bind a thread to a given core, this can improve performance (this libra OpenHFT Java Thread Affinity library -See https://github.com/OpenHFT/Java-Thread-Affinity/tree/master/affinity/src/test/java[affinity/src/test/java] +See https://github.com/OpenHFT/Java-Thread-Affinity/tree/master/affinity/src/test/java[affinity/src/test/java] for working examples of how to use this library. +=== Supported operating systems + +The library detects the running platform in `Affinity.java` and selects an implementation for that OS. +Features differ between systems: + +* *Linux* - full affinity control via JNA. +The implementation can get and set thread affinity, query the current CPU, and obtain process and thread IDs. +* *Windows* - thread affinity is managed through the kernel API. +Process and thread IDs are available, while `getCpu()` returns `-1`. +* *macOS* - provides process and thread IDs but does not modify affinity and reports the CPU id as `-1`. +* *Solaris* - mirrors the macOS implementation: only process and thread IDs are returned with no affinity or CPU querying support. + === Changes * V3.2.0 - Add support for text configuration @@ -25,19 +37,15 @@ for working examples of how to use this library. === Dependencies -Java-Thread-Affinity will try to use https://github.com/java-native-access/jna[JNA] -to provide access to native thread-handling functions. JNA should be installed on -your system to get the most from this library. +Java-Thread-Affinity will try to use link:https://github.com/java-native-access/jna[JNA] +to provide access to native thread-handling functions. +JNA should be installed on your system to get the most from this library. === JNA version -Java-Thread-Affinity currently depends on JNA version 4.4.0, which in turn -depends on a version of GLIBC >= 2.14. If your operating system is an old one, -with a version of GLIBC released before 2011, this library will not be able to -invoke native functions. +Java-Thread-Affinity currently depends on JNA version 4.4.0, which in turn depends on a version of GLIBC >= 2.14. If your operating system is an old one, with a version of GLIBC released before 2011, this library will not be able to invoke native functions. -To work around this problem, fork the repository, and override the `` tag -for the artifacts `jna` and `jna-platform` in the project's `pom` file. +To work around this problem, fork the repository, and override the `` tag for the artifacts `jna` and `jna-platform` in the project's `pom` file. === Installing JNA on Ubuntu @@ -47,14 +55,32 @@ for the artifacts `jna` and `jna-platform` in the project's `pom` file. sudo yum install jna +=== Installing JNA on Windows + + choco install jna + +Or download jna.jar and jna-platform.jar from the JNA project and add them to your classpath. + === How does CPU allocation work? -The library will read your `/proc/cpuinfo` if you have one or provide one and it will determine your CPU layout. If you don't have one it will assume every CPU is on one CPU socket. +The library will read your `/proc/cpuinfo` if you have one or provide one and it will determine your CPU layout. +If you don't have one it will assume every CPU is on one CPU socket. -The library looks for isolated CPUs determined by looking at the CPUs you are not running on by default. +The library looks for isolated CPUs determined by looking at the CPUs you are not running on by default. i.e. if you have 16 CPUs but 8 of them are not available for general use (as determined by the affinity of the process on startup) it will start assigning to those CPUs. Note: if you have more than one process using this library you need to specify which CPUs the process can use otherwise it will assign the same CPUs to both processes. -To control which CPUs a process can use, add -Daffinity.reserved={cpu-mask-in-hex} to the command line of the process. + +To control which CPUs a process can use, add `-Daffinity.reserved={cpu-mask-in-hex}` to the command line of the process. +The mask is a hexadecimal bit mask without the `0x` prefix where bit `0` represents CPU `0`, bit `1` represents CPU `1` and so on. +Multiple CPUs can be specified by setting more than one bit. + +For example: + +* `-Daffinity.reserved=2` reserves only CPU `1`. +* `-Daffinity.reserved=6` reserves CPUs `1` and `2`. +* `-Daffinity.reserved=10` reserves CPUs `1` and `3` (hexadecimal `a`). + +Use an appropriate mask when starting each process to avoid reserving the same cores for multiple JVMs. Note: the CPU 0 is reserved for the Operating System, it has to run somewhere. @@ -64,25 +90,41 @@ https://github.com/peter-lawrey/Java-Thread-Affinity/wiki/Getting-started https://github.com/peter-lawrey/Java-Thread-Affinity/wiki/How-it-works -http://vanillajava.blogspot.co.uk/2013/07/micro-jitter-busy-waiting-and-binding.html +https://vanillajava.blogspot.com/2013/07/micro-jitter-busy-waiting-and-binding.html === isolcpus Java-Thread-Affinity requires that you first isolate some CPU's. -Once a CPU core is isolated, the Linux scheduler will not use the CPU core to run any user-space processes. The isolated CPUs will not participate in load balancing, and will not have tasks running on them unless explicitly assigned. +Once a CPU core is isolated, the Linux scheduler will not use the CPU core to run any user-space processes. +The isolated CPUs will not participate in load balancing, and will not have tasks running on them unless explicitly assigned. To isolate the 1st and 3rd CPU cores (CPU numbers start from 0) on your system, add the following to the kernel command line during boot: isolcpus=1,3 +.Using GRUB +[source] +---- +sudo sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT="/GRUB_CMDLINE_LINUX_DEFAULT="isolcpus=1,3 /' /etc/default/grub +sudo update-grub +sudo reboot +---- + +.Using systemd-boot +[source] +---- +sudo sed -i 's/^options \(.*\)/options \1 isolcpus=1,3/' /boot/loader/entries/*.conf +sudo reboot +---- + == Using AffinityLock === Acquiring a CPU lock for a thread You can acquire a lock for a CPU in the following way: -In Java 6 -[source, java] +.In Java 6 +[source,java] ---- AffinityLock al = AffinityLock.acquireLock(); try { @@ -92,26 +134,32 @@ try { } ---- -In Java 7 or 8 -[source, java] +.In Java 7 or 8 +[source,java] ---- try (AffinityLock al = AffinityLock.acquireLock()) { // do some work while locked to a CPU. } ---- + You have further options such as === Acquiring a CORE lock for a thread -You can reserve a whole core. If you have hyper-threading enabled, this will use one CPU and leave it's twin CPU unused. -[source, java] +You can reserve a whole core. +If you have hyper-threading enabled, this will use one CPU and leave it's twin CPU unused. + +[source,java] ---- try (AffinityLock al = AffinityLock.acquireCore()) { // do some work while locked to a CPU. } ---- + === Controlling layout + You can chose a layout relative to an existing lock. -[source, java] + +[source,java] ---- try (final AffinityLock al = AffinityLock.acquireLock()) { System.out.println("Main locked"); @@ -127,45 +175,104 @@ try (final AffinityLock al = AffinityLock.acquireLock()) { t.start(); } ---- -In this example, the library will prefer a free CPU on the same Socket as the first thread, otherwise it will pick any free CPU. -=== Getting the thread id. +In this example, the library will prefer a free CPU on the same Socket as the first thread, otherwise it will pick any free CPU. + +=== Affinity strategies +The `AffinityStrategies` enum defines hints for selecting a CPU relative to an existing lock. + +[options="header",cols="1,3"] +|=== +| Strategy | Meaning + +|`ANY`|Use any available CPU. +|`SAME_CORE`|Select a CPU on the same core. +|`SAME_SOCKET`|Select a CPU on the same socket but a different core. +|`DIFFERENT_CORE`|Select a CPU on another core (possibly another socket). +|`DIFFERENT_SOCKET`|Select a CPU on a different socket. +|=== + +=== Getting the thread id You can get the current thread id using -[source, java] ----- +[source,java] +---- int threadId = AffinitySupport.getThreadId(); ---- -=== Determining which CPU you are running on. + +=== Determining which CPU you are running on You can get the current CPU being used by -[source, java] ----- +[source,java] +---- int cpuId = AffinitySupport.getCpu(); ---- -=== Controlling the affinity more directly. + +=== Controlling the affinity more directly + The affinity of the process on start up is -[source, java] ----- +[source,java] +---- long baseAffinity = AffinityLock.BASE_AFFINITY; ---- + The available CPU for reservation is -[source, java] + +[source,java] ---- long reservedAffinity = AffinityLock.RESERVED_AFFINITY; ---- + If you want to get/set the affinity directly you can do -[source, java] ----- + +[source,java] +---- long currentAffinity = AffinitySupport.getAffinity(); AffinitySupport.setAffinity(1L << 5); // lock to CPU 5. ----- +---- + +=== Understanding dumpLocks() output + +Several examples print the current CPU assignments using `AffinityLock.dumpLocks()`. +Each line of the output begins with the zero based CPU id followed by the status of that CPU. +Example output might look like: + +[source] +---- +0: Reserved for this application +1: Thread[reader,5,main] alive=true +2: General use CPU +3: CPU not available +---- + +The number on each line is the logical CPU index as recognised by the library. +The text after the colon describes whether that CPU is free, reserved or already bound to a thread. +Use these indices when calling `AffinityLock.acquireLock(n)` +or when constructing explicit affinity masks. + +=== Lock file directory + +AffinityLock stores a small lock file for each CPU. +These files are placed in the directory specified by the `java.io.tmpdir` system property, which by default points to your system's temporary directory (usually `/tmp` on Linux). + +If you want to keep the lock files elsewhere, set this property before using any affinity APIs: + +[source,bash] +---- +java -Djava.io.tmpdir=/path/to/dir ... +---- + +or in code + +[source,java] +---- +System.setProperty("java.io.tmpdir", "/path/to/dir"); +---- === Debugging affinity state -For a detailed of view of the current affinity state (as seen by the library), -execute the following script on Linux systems: +For a detailed of view of the current affinity state (as seen by the library), execute the following script on Linux systems: [source] ---- @@ -185,6 +292,24 @@ $ for i in "$(ls cpu-*)"; ---- +== Using AffinityThreadFactory + +`AffinityThreadFactory` binds each thread it creates according to a set of `AffinityStrategy` rules. +This allows executors to automatically run tasks on cores selected by the library. + +[source,java] +---- +ExecutorService es = Executors.newFixedThreadPool(4, + new AffinityThreadFactory("worker", + AffinityStrategies.SAME_CORE, + AffinityStrategies.DIFFERENT_SOCKET, + AffinityStrategies.ANY)); +es.submit(() -> { + // your task here +}); +System.out.println("\nThe assignment of CPUs is\n" + AffinityLock.dumpLocks()); +---- + == Support Material https://groups.google.com/forum/?hl=en-GB#!forum/java-thread-affinity[Java Thread Affinity support group] @@ -194,12 +319,14 @@ For an article on how much difference affinity can make and how to use it http:/ == Questions and Answers === Question: How to lock a specific cpuId -I am currently working on a project related to deadlock detection in multithreaded programs in java. We are trying to run threads on different processors and thus came across your github posts regarding the same. https://github.com/peter-lawrey/Java-Thread-Affinity/wiki/Getting-started -Being a beginner, I have little knowledge and thus need your assistance. We need to know how to run threads on specified cpu number and then switch threads when one is waiting. +I am currently working on a project related to deadlock detection in multithreaded programs in java. +We are trying to run threads on different processors and thus came across your github posts regarding the same. https://github.com/peter-lawrey/Java-Thread-Affinity/wiki/Getting-started +Being a beginner, I have little knowledge and thus need your assistance. +We need to know how to run threads on specified cpu number and then switch threads when one is waiting. === Answer -[source, java] +[source,java] ---- // lock a cpuId try (AffinityLock lock = AffinityLock.acquireLock(n)) { @@ -223,7 +350,7 @@ try (AffinityLock lock = AffinityLock.acquireLockLastMinus(n)) { I have the cpuId in a configuration file, how can I set it using a string? -=== Answer: use one of the following. +=== Answer: use one of the following [source,java] ---- diff --git a/affinity-test/pom.xml b/affinity-test/pom.xml index b085626b3..3ce950e34 100644 --- a/affinity-test/pom.xml +++ b/affinity-test/pom.xml @@ -1,19 +1,9 @@ + + Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + +--> 4.0.0 @@ -21,12 +11,12 @@ net.openhft java-parent-pom - 1.1.23 + 2026.0 affinity-test - 3.21ea2-SNAPSHOT + 2026.3-SNAPSHOT bundle OpenHFT/Java-Thread-Affinity/affinity-test @@ -34,6 +24,8 @@ UTF-8 + 0 + 0 @@ -41,8 +33,8 @@ net.openhft third-party-bom + 2026.0 pom - 3.19.4 import @@ -59,8 +51,8 @@ affinity - javax.inject - javax.inject + jakarta.inject + jakarta.inject-api test @@ -202,8 +194,10 @@ - scm:git:https://github.com/OpenHFT/Java-Thread-Affinity.git - HEAD + scm:git:git@github.com:OpenHFT/Java-Thread-Affinity.git + ea + scm:git:git@github.com:OpenHFT/Java-Thread-Affinity.git + scm:git:git@github.com:OpenHFT/Java-Thread-Affinity.git diff --git a/affinity-test/src/main/java/net/openhft/affinity/osgi/OSGiPlaceholder.java b/affinity-test/src/main/java/net/openhft/affinity/osgi/OSGiPlaceholder.java index becba5df0..a6e7ef36d 100644 --- a/affinity-test/src/main/java/net/openhft/affinity/osgi/OSGiPlaceholder.java +++ b/affinity-test/src/main/java/net/openhft/affinity/osgi/OSGiPlaceholder.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.osgi; /** diff --git a/affinity-test/src/test/java/net/openhft/affinity/osgi/OSGiBundleTest.java b/affinity-test/src/test/java/net/openhft/affinity/osgi/OSGiBundleTest.java index 727ea8c28..a3213a6e7 100644 --- a/affinity-test/src/test/java/net/openhft/affinity/osgi/OSGiBundleTest.java +++ b/affinity-test/src/test/java/net/openhft/affinity/osgi/OSGiBundleTest.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.osgi; import org.junit.Ignore; @@ -35,7 +21,7 @@ @RunWith(PaxExam.class) public class OSGiBundleTest extends net.openhft.affinity.osgi.OSGiTestBase { @Inject - BundleContext context; + private BundleContext context; @Configuration public Option[] config() { @@ -64,7 +50,7 @@ public void checkInject() { public void checkBundleState() { final Bundle bundle = findBundle(context, "net.openhft.affinity"); assertNotNull(bundle); - assertEquals(bundle.getState(), Bundle.ACTIVE); + assertEquals(Bundle.ACTIVE, bundle.getState()); } @Test diff --git a/affinity-test/src/test/java/net/openhft/affinity/osgi/OSGiTestBase.java b/affinity-test/src/test/java/net/openhft/affinity/osgi/OSGiTestBase.java index deb9d9d5f..a5fb702dd 100644 --- a/affinity-test/src/test/java/net/openhft/affinity/osgi/OSGiTestBase.java +++ b/affinity-test/src/test/java/net/openhft/affinity/osgi/OSGiTestBase.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.osgi; import org.ops4j.pax.exam.CoreOptions; @@ -25,11 +11,11 @@ import java.io.File; -public class OSGiTestBase { +class OSGiTestBase { - public static Option workspaceBundle(String projectName) { + static Option workspaceBundle(String projectName) { String baseDir = System.getProperty("main.basedir"); - String bundleDir = null; + String bundleDir; bundleDir = String.format("%s/%s/target/classes", baseDir, projectName); if (new File(bundleDir).exists()) { @@ -44,11 +30,11 @@ public static Option workspaceBundle(String projectName) { return null; } - public static MavenArtifactProvisionOption mavenBundleAsInProject(final String groupId, final String artifactId) { + static MavenArtifactProvisionOption mavenBundleAsInProject(final String groupId, final String artifactId) { return CoreOptions.mavenBundle().groupId(groupId).artifactId(artifactId).versionAsInProject(); } - public static Bundle findBundle(BundleContext context, String symbolicName) { + static Bundle findBundle(BundleContext context, String symbolicName) { Bundle[] bundles = context.getBundles(); for (Bundle bundle : bundles) { if (bundle != null) { @@ -61,4 +47,3 @@ public static Bundle findBundle(BundleContext context, String symbolicName) { return null; } } - diff --git a/affinity/pom.xml b/affinity/pom.xml index 56be3e094..33f352b16 100644 --- a/affinity/pom.xml +++ b/affinity/pom.xml @@ -1,19 +1,9 @@ + + Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + +--> 4.0.0 @@ -21,12 +11,12 @@ net.openhft java-parent-pom - 1.1.23 + 2026.0 affinity - 3.21ea2-SNAPSHOT + 2026.3-SNAPSHOT bundle OpenHFT/Java-Thread-Affinity/affinity @@ -35,6 +25,8 @@ src/main/c UTF-8 + 0.40 + 0.4 @@ -42,8 +34,15 @@ net.openhft third-party-bom + 2026.0 + pom + import + + + net.openhft + chronicle-bom + 2026.0-SNAPSHOT pom - 3.19.4 import @@ -82,6 +81,11 @@ slf4j-simple test + + net.openhft + chronicle-test-framework + test + @@ -95,42 +99,60 @@ + + make-c + + + linux + !arm + + + !dontMake + + + + + + + org.codehaus.mojo + exec-maven-plugin + + + + build-native + process-classes + + exec + + + make + ${project.basedir}/${native.source.dir} + + + + + + + + org.apache.maven.plugins maven-compiler-plugin - -Xlint:deprecation + + -h + ${project.build.directory}/jni + -Xlint:all,-options + 1.8 1.8 UTF-8 - - - - org.codehaus.mojo - exec-maven-plugin - 1.2.1 - - - - build-native - process-classes - - exec - - - ${project.basedir}/${native.source.dir}/Makefile - ${project.basedir}/${native.source.dir} - - - - - org.apache.maven.plugins maven-scm-publish-plugin @@ -198,14 +220,21 @@ - - sonatype-nexus-staging - https://oss.sonatype.org/content/repositories/staging + + true + + chronicle-enterprise-snapshots + Snapshot Repository + https://nexus.chronicle.software/content/repositories/snapshots + + true + chronicle-enterprise-release + https://nexus.chronicle.software/content/repositories/releases @@ -214,7 +243,7 @@ scm:git:git@github.com:OpenHFT/Java-Thread-Affinity.git scm:git:git@github.com:OpenHFT/Java-Thread-Affinity.git - ea + ea diff --git a/affinity/src/main/adoc/requirements.adoc b/affinity/src/main/adoc/requirements.adoc new file mode 100644 index 000000000..8132561a5 --- /dev/null +++ b/affinity/src/main/adoc/requirements.adoc @@ -0,0 +1,304 @@ += Requirements Document: Java Thread Affinity +:toc: + +== 1. Introduction + +This document outlines the requirements for the *Java Thread Affinity* library. +The primary purpose of this library is to provide Java applications with the capability to control Central Processing Unit (CPU) affinity for their threads. +This allows developers to bind specific threads to designated CPU cores, which can lead to performance improvements, especially in latency-sensitive applications, by reducing context switching and improving cache utilisation. + +The library aims to offer a cross-platform API, with the most comprehensive support for Linux systems, leveraging Java Native Access (JNA) and, where applicable, Java Native Interface (JNI) for low-level system interactions. + +== 2. Scope + +The scope of the Java Thread Affinity project includes: + +* Providing mechanisms to get and set thread affinity on supported operating systems. +* Offering a CPU locking mechanism (`AffinityLock`) to manage core reservations for threads or entire cores. +* Detecting or allowing specification of the CPU layout (sockets, cores, threads per core). +* Providing a high-resolution timer. +* Supporting inter-process lock checking for CPU core reservations. +* Delivering a thread factory that assigns affinity to newly created threads. +* Packaging the core library and an OSGi-compatible test bundle. + +== 3. Definitions, Acronyms, and Abbreviations + +CPU :: Central Processing Unit +JNA :: Java Native Access +JNI :: Java Native Interface +OS :: Operating System +PID :: Process Identifier +OSGi :: Open Service Gateway initiative +POM :: Project Object Model (Maven) +API :: Application Programming Interface + +== 4. References + +* Project Repository: link:https://github.com/OpenHFT/Java-Thread-Affinity[] +* JNA: link:https://github.com/java-native-access/jna[] + +== 5. Project Overview + +The *Java Thread Affinity* library enables fine-grained control over which CPU cores Java threads execute on. +This is particularly beneficial for high-performance computing and low-latency applications where minimising jitter and maximising cache efficiency is critical. +The library abstracts OS-specific details, providing a unified Java API. + +=== 5.1. Purpose + +* To allow Java threads to be bound to specific CPU cores. +* To provide tools for understanding and managing CPU topology from within a Java application. +* To offer a high-resolution timing mechanism. + +=== 5.2. Benefits + +* _Performance Improvement_: Reduced thread migration and context switching. +* _Cache Efficiency_: Better utilisation of CPU caches (L1, L2, L3). +* _Jitter Reduction_: More predictable thread execution times. + +== 6. Functional Requirements + +=== 6.1. Core Affinity Control (net.openhft.affinity.Affinity) + +* *FR1*: The system _shall_ allow setting the affinity of the current thread to a specific CPU core or a set of cores (BitSet). +** `Affinity.setAffinity(BitSet affinity)` +** `Affinity.setAffinity(int cpu)` +* *FR2*: The system _shall_ allow retrieving the current affinity mask of the current thread. +** `Affinity.getAffinity()` +* *FR3*: The system _shall_ allow querying the logical CPU ID the current thread is running on. +** `Affinity.getCpu()` +* *FR4*: The system _shall_ allow retrieving the native process ID of the current Java process. +** `IAffinity.getProcessId()` +* *FR5*: The system _shall_ allow retrieving the native thread ID of the current Java thread. +** `IAffinity.getThreadId()` +** `Affinity.setThreadId()` (to update `Thread.tid` via reflection if available) + +=== 6.2. CPU Lock Management (net.openhft.affinity.AffinityLock) + +* *FR6.1*: The system _shall_ provide a mechanism to acquire an exclusive lock on an available CPU core for the current thread. +** `AffinityLock.acquireLock()` +** `AffinityLock.acquireLock(boolean bind)` +** `AffinityLock.acquireLock(int cpuId)` +** `AffinityLock.acquireLock(String desc)` (e.g., "last", "last-N", "N", "any", "none", "csv:1,2,3") +* *FR6.2*: The system _shall_ provide a mechanism to acquire an exclusive lock on an entire physical core (including all its logical CPUs/hyper-threads). +** `AffinityLock.acquireCore()` +** `AffinityLock.acquireCore(boolean bind)` +* *FR6.3*: Acquired locks _shall_ be releasable, restoring the thread's affinity to a base state or a defined default. +** `AffinityLock.release()` +** `AffinityLock.close()` (for try-with-resources) +* *FR6.4*: The system _shall_ support affinity strategies for acquiring new locks relative to existing locks (e.g., same core, same socket, different core, different socket). +** `AffinityLock.acquireLock(AffinityStrategy... strategies)` +** `AffinityStrategies` enum: `ANY`, `SAME_CORE`, `SAME_SOCKET`, `DIFFERENT_CORE`, `DIFFERENT_SOCKET`. +* *FR6.5*: The system _shall_ provide a method to dump the current status of all CPU locks managed by the library. +** `AffinityLock.dumpLocks()` +* *FR6.6*: The system _shall_ allow querying if a lock is allocated and bound. +** `AffinityLock.isAllocated()` +** `AffinityLock.isBound()` + +=== 6.3. CPU Layout Detection (net.openhft.affinity.CpuLayout) + +* *FR7.1*: On Linux, the system _shall_ attempt to automatically detect the CPU layout (sockets, cores per socket, threads per core) by parsing `/proc/cpuinfo`. +** `VanillaCpuLayout.fromCpuInfo()` +* *FR7.2*: The system _shall_ allow applications to programmatically define the CPU layout. +** `AffinityLock.cpuLayout(CpuLayout cpuLayout)` +* *FR7.3*: The CPU layout _shall_ provide information about: +** Total number of logical CPUs: `CpuLayout.cpus()` +** Number of sockets: `CpuLayout.sockets()` +** Cores per socket: `CpuLayout.coresPerSocket()` +** Threads per core: `CpuLayout.threadsPerCore()` +** Mapping a logical CPU ID to its socket, core, and thread ID: `socketId(int)`, `coreId(int)`, `threadId(int)`. +** Hyper-threaded pair for a CPU: `pair(int)`. + +=== 6.4. High-Resolution Timer (net.openhft.ticker.Ticker) + +* *FR8.1*: The system _shall_ provide a high-resolution time source. +** `Ticker.ticks()` (raw timer ticks) +** `Ticker.nanoTime()` (ticks converted to nanoseconds) +* *FR8.2*: If native JNI components are available and loaded (specifically `libCEInternals.so`), the timer _shall_ attempt to use `rdtsc` (Read Time-Stamp Counter). +** `JNIClock.rdtsc0()` +* *FR8.3*: If JNI-based `rdtsc` is not available, the timer _shall_ fall back to `System.nanoTime()`. +** `SystemClock.INSTANCE` +* *FR8.4*: The timer _shall_ provide methods to convert ticks to nanoseconds and microseconds. +** `ITicker.toNanos(long ticks)` +** `ITicker.toMicros(double ticks)` + +=== 6.5. OS-Specific Implementations (net.openhft.affinity.impl) + +* *FR9.1*: The system _shall_ provide tailored implementations of `IAffinity` for different operating systems: +** *Linux*: Full affinity control, CPU ID, Process ID, Thread ID via JNA (`LinuxJNAAffinity`, `PosixJNAAffinity`) or JNI (`NativeAffinity`). +** *Windows*: Thread affinity control, Process ID, Thread ID via JNA (`WindowsJNAAffinity`). `getCpu()` returns -1. +** *macOS*: Process ID, Thread ID via JNA (`OSXJNAAffinity`). +No affinity modification; `getCpu()` returns -1. +** *Solaris*: Process ID, Thread ID via JNA (`SolarisJNAAffinity`). +No affinity modification; `getCpu()` returns -1. +* *FR9.2*: A `NullAffinity` implementation _shall_ be used as a fallback if no suitable native implementation can be loaded or for unsupported OS. + +=== 6.6. Affinity Thread Factory (net.openhft.affinity.AffinityThreadFactory) + +* *FR10.1*: The system _shall_ provide a `ThreadFactory` that assigns affinity to newly created threads based on specified `AffinityStrategy` rules. +** `new AffinityThreadFactory(String name, AffinityStrategy... strategies)` +* *FR10.2*: If no strategies are provided, `AffinityStrategies.ANY` _shall_ be used by default. + +=== 6.7. Inter-Process Lock Checking (net.openhft.affinity.lockchecker) + +* *FR11.1*: On Linux, the system _shall_ provide a mechanism to check if a specific CPU core is free or already locked by another process. +** `LockCheck.isCpuFree(int cpu)` +* *FR11.2*: This mechanism _shall_ use file-based locks located in the directory specified by the `java.io.tmpdir` system property. +** `FileLockBasedLockChecker` +* *FR11.3*: The system _shall_ allow obtaining and releasing these inter-process locks for specified CPU IDs. +** `LockChecker.obtainLock(int id, int id2, String metaInfo)` +** `LockChecker.releaseLock(int id)` +* *FR11.4*: The system _shall_ store meta-information (e.g., PID of the locking process) within the lock file and allow its retrieval. +** `LockChecker.getMetaInfo(int id)` + +=== 6.8. Native Code Compilation (C/C++) + +* *FR12.1*: The system _shall_ include C/C++ source code for native functions required for affinity and timer operations on Linux and macOS. + ** `software_chronicle_enterprise_internals_impl_NativeAffinity.cpp` (Linux) + ** `software_chronicle_enterprise_internals_impl_NativeAffinity_MacOSX.c` (macOS) + ** `net_openhft_ticker_impl_JNIClock.cpp` (for `rdtsc`) +* *FR12.2*: A Makefile _shall_ be provided to compile the native C/C++ code into a shared library (`libCEInternals.so`). +* *FR12.3*: The Java code _shall_ load this native library if available. +** `software.chronicle.enterprise.internals.impl.NativeAffinity.loadAffinityNativeLibrary()` + +== 7. Non-Functional Requirements + +* *NFR1. Platform Support*: +** *Primary Support*: Linux (full functionality). +** *Partial Support*: Windows (affinity setting, PID/TID, no `getCpu()`). +** *Limited Support*: macOS, Solaris (PID/TID only, no affinity setting or `getCpu()`). +* *NFR2. Dependencies*: +** *JNA*: `net.java.dev.jna:jna`, `net.java.dev.jna:jna-platform`. +Version 5.x or higher is recommended for full functionality. +The project currently uses version 4.4.0 (as per README, though POMs might show updates). +** *SLF4J API*: `org.slf4j:slf4j-api` for logging. +** *JetBrains Annotations*: `org.jetbrains:annotations` for code quality. +* *NFR3. Performance*: The library _should_ introduce minimal overhead. +Native calls _should_ be efficient. +The primary goal is to enable performance improvements in the client application. +* *NFR4. Licensing*: The project _shall_ be licensed under the Apache License, Version 2.0. +* *NFR5. Build System*: The project _shall_ use Apache Maven for building and dependency management. +* *NFR6. Language*: +** Core library _shall_ be implemented in Java (1.8+ as per POM). +** Native components _shall_ be implemented in C/C++. +* *NFR7. Usability*: +** The API _should_ be clear and relatively simple to use. +** Javadoc _shall_ be provided for public APIs. +** Example usage _shall_ be available (e.g., in test sources and README). +* *NFR8. Error Handling and Resilience*: +** The library _shall_ degrade gracefully if JNA or native libraries are not available or if an OS does not support certain features (e.g., falling back to `NullAffinity`). +** Errors during native calls _should_ be appropriately logged and/or propagated as exceptions. +* *NFR9. Configuration*: +** Reserved CPUs for the application _shall_ be configurable via the system property `affinity.reserved={hex-mask}`. +** The lock file directory _shall_ default to `java.io.tmpdir` and be overridable by setting this system property. +* *NFR10. OSGi Support*: The `affinity-test` module _shall_ be packaged as an OSGi bundle, demonstrating OSGi compatibility. +* *NFR11. Language Style*: Code and documentation _shall_ use British English, except for established technical US spellings (e.g., `synchronized`). + +== 8. System Architecture + +=== 8.1. High-Level Architecture + +The Java Thread Affinity library is a Java-based system that interfaces with the underlying operating system through JNA (primarily) and JNI (for specific `libCEInternals.so` functionalities). +It abstracts OS-specific system calls related to thread affinity, CPU information, and timing. + +=== 8.2. Key Components + +* *`net.openhft.affinity.Affinity`*: Main public API facade for basic affinity operations. +* *`net.openhft.affinity.IAffinity`*: Interface defining the contract for OS-specific implementations. +** Concrete Implementations: `LinuxJNAAffinity`, `WindowsJNAAffinity`, `OSXJNAAffinity`, `SolarisJNAAffinity`, `PosixJNAAffinity`, `NativeAffinity` (JNI), `NullAffinity`. +* *`net.openhft.affinity.AffinityLock`*: Manages CPU reservations and bindings. +* *`net.openhft.affinity.LockInventory`*: Tracks the state of CPU locks based on `CpuLayout`. +* *`net.openhft.affinity.CpuLayout`*: Interface for CPU topology information. +** `VanillaCpuLayout`: Parses `/proc/cpuinfo` or properties files. +** `NoCpuLayout`: Default layout if detection fails. +* *`net.openhft.affinity.AffinityStrategies`*: Enum defining strategies for selecting CPUs. +* *`net.openhft.affinity.AffinityThreadFactory`*: A `java.util.concurrent.ThreadFactory` that sets affinity for new threads. +* *`net.openhft.ticker.Ticker`*: Provides high-resolution time. +** `JNIClock`: Uses `rdtsc` via JNI. +** `SystemClock`: Uses `System.nanoTime()`. +* *`net.openhft.affinity.lockchecker.LockChecker`*: Interface for inter-process lock management. +** `FileLockBasedLockChecker`: Implementation using file system locks. +* *Native Code (`src/main/c`)*: C/C++ sources for `libCEInternals.so` providing functions like `getAffinity0`, `setAffinity0` (Linux JNI), `rdtsc0`. + +=== 8.3. Maven Modules + +* *`Java-Thread-Affinity` (Parent POM)*: Aggregates sub-modules. +** Group ID: `net.openhft` +** Artifact ID: `Java-Thread-Affinity` +* *`affinity` (Core Library)*: Contains the main library code, JNA/JNI integrations, and native sources. +** Artifact ID: `affinity` +** Packaging: `bundle` (OSGi compatible) +* *`affinity-test` (Test Module)*: Contains OSGi integration tests and example usage. +** Artifact ID: `affinity-test` +** Packaging: `bundle` + +== 9. Native Components (libCEInternals.so) + +The library can utilise an optional native shared library, `libCEInternals.so`, for certain operations, primarily on Linux. + +* *Purpose*: Provides direct JNI implementations for thread affinity and the `rdtsc` timer. +* *Source Location*: `Java-Thread-Affinity/affinity/src/main/c/` +* *Build*: Compiled using the `Makefile` in the source directory (typically invoked by Maven's `exec-maven-plugin`). +* *Key Native Functions Implemented*: +** `Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getAffinity0` +** `Java_software_chronicle_enterprise_internals_impl_NativeAffinity_setAffinity0` +** `Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getCpu0` +** `Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getProcessId0` +** `Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getThreadId0` +** `Java_net_openhft_ticker_impl_JNIClock_rdtsc0` +* *Platform Specifics*: +** *Linux*: Uses `sched_getaffinity`, `sched_setaffinity`, `sched_getcpu`, `getpid`, `syscall(SYS_gettid)`. +** *macOS*: (Separate C file `software_chronicle_enterprise_internals_impl_NativeAffinity_MacOSX.c`) Uses `pthread_mach_thread_np`, `thread_policy_get`, `thread_policy_set`. +Note: JNA implementations are generally preferred on macOS. +* *Loading*: The `NativeAffinity.java` class attempts to load `System.loadLibrary("CEInternals")`. + +== 10. API Overview + +A brief overview of the primary public classes and interfaces: + +* *`net.openhft.affinity.Affinity`*: +** Static utility methods for basic affinity operations: `getAffinity()`, `setAffinity(BitSet)`, `setAffinity(int cpu)`, `getCpu()`, `getThreadId()`. +** Manages selection of the appropriate `IAffinity` implementation. +* *`net.openhft.affinity.AffinityLock`*: +** Manages acquisition and release of CPU locks: `acquireLock()`, `acquireCore()`, `release()`, `close()`. +** Configures CPU layout: `cpuLayout(CpuLayout)`. +** Provides information about reserved CPUs: `BASE_AFFINITY`, `RESERVED_AFFINITY`. +* *`net.openhft.affinity.AffinityStrategies`*: +** Enum defining CPU selection strategies for `AffinityLock`. +* *`net.openhft.affinity.CpuLayout`*: +** Interface to describe the machine's CPU topology. +* *`net.openhft.affinity.IAffinity`*: +** Core interface implemented by OS-specific providers. +* *`net.openhft.ticker.Ticker`*: +** Static utility for accessing high-resolution time: `ticks()`, `nanoTime()`. +* *`net.openhft.affinity.AffinityThreadFactory`*: +** Implements `java.util.concurrent.ThreadFactory` to create threads with specific affinity settings. + +== 11. Build and Deployment + +* The project is built using Apache Maven. +* The main artifact `net.openhft:affinity` is an OSGi bundle. +* Dependencies are managed via `pom.xml` files, including a `third-party-bom` and `chronicle-bom`. +* The `make-c` profile in `affinity/pom.xml` triggers the compilation of native C code using `make`. +* The `maven-bundle-plugin` is used to generate OSGi manifest information. +* The `maven-scm-publish-plugin` is configured for publishing Javadoc to `gh-pages`. + +== 12. Testing + +The project includes a comprehensive suite of tests: + +* *Unit Tests*: Located in `affinity/src/test/java/`. +** `NativeAffinityTest`, `JnaAffinityTest`: Test core JNI/JNA functionalities. +** `AffinityLockTest`: Tests `AffinityLock` logic, including descriptions and inter-thread lock acquisition. +** `VanillaCpuLayoutTest`, `VanillaCpuLayoutPropertiesParseTest`: Test parsing of `cpuinfo` files and properties files for CPU layout. +** `TickerTest`, `JNIClockTest`: Test timer implementations. +** `LockCheckTest`, `FileLockLockCheckTest`: Test inter-process lock checking. +** `MultiProcessAffinityTest`: Tests affinity locking behavior across multiple Java processes. +* *OSGi Bundle Tests*: Located in `affinity-test/src/test/java/net/openhft/affinity/osgi/`. +** `OSGiBundleTest`: Verifies bundle activation and package exports in an OSGi environment using Pax Exam. +* *Test Resources*: Includes sample `cpuinfo` files for various architectures and corresponding properties files to test layout parsing. +** `affinity/src/test/resources/` +* *Test Infrastructure*: +** `BaseAffinityTest`: Provides common setup for tests, including temporary folder management for lock files. +** `chronicle-test-framework`: Used for some test utilities, like `JavaProcessBuilder` for multi-process tests. + +The tests cover various aspects including basic affinity setting, CPU layout parsing, lock management, multi-threading scenarios, multi-process contention, and OSGi integration. diff --git a/affinity/src/main/c/Makefile b/affinity/src/main/c/Makefile index 58a06e53a..ac448e568 100755 --- a/affinity/src/main/c/Makefile +++ b/affinity/src/main/c/Makefile @@ -1,4 +1,3 @@ -#!/usr/bin/make -f # # Makefile for C code # @@ -10,21 +9,29 @@ TARGET := $(TARGET_DIR)/libCEInternals.so WORKING_DIR := $(TARGET_DIR)/../jni +JNI_OS := win32 +UNAME_S:= $(shell uname -s) +ifeq ($(UNAME_S), Linux) + JNI_OS := linux + LRT := -lrt +endif +ifeq ($(UNAME_S), Darwin) + JNI_OS := darwin +endif + JAVA_CLASSES = software.chronicle.enterprise.internals.impl.NativeAffinity net.openhft.ticker.impl.JNIClock JNI_STUBS := $(subst .,_,$(JAVA_CLASSES)) -JNI_HEADERS := $(patsubst %,$(WORKING_DIR)/%.h,$(JNI_STUBS)) JNI_SOURCES := $(patsubst %,%.cpp,$(JNI_STUBS)) -JNI_JAVA_SOURCES := $(patsubst %,$(TARGET_DIR)/%.class,$(subst .,/,$(JAVA_CLASSES))) JAVA_BUILD_DIR := $(TARGET_DIR) JAVA_HOME ?= /usr/java/default JAVA_LIB := $(JAVA_HOME)/jre/lib -JVM_SHARED_LIBS := -L$(JAVA_LIB)/amd64/server -L$(JAVA_LIB)/i386/server -L$(JAVA_LIB)/amd64/jrockit/ -L$(JAVA_LIB)/i386/jrockit/ -L$(JAVA_LIB)/ppc64le/server -L$(JAVA_LIB)/ppc64le/jrockit/ +JVM_SHARED_LIBS := -L"$(JAVA_LIB)/amd64/server" -L"$(JAVA_LIB)/i386/server" -L"$(JAVA_LIB)/amd64/jrockit" -L"$(JAVA_LIB)/i386/jrockit" -L"$(JAVA_LIB)/ppc64le/server" -L"$(JAVA_LIB)/ppc64le/jrockit" -L"$(JAVA_HOME)/lib/server" CXX=g++ -INCLUDES := -I $(JAVA_HOME)/include -I $(JAVA_HOME)/include/linux -I $(WORKING_DIR) +INCLUDES := -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/$(JNI_OS)" -I"$(WORKING_DIR)" # classpath for javah ifdef CLASSPATH @@ -37,12 +44,8 @@ endif all: $(TARGET) -$(TARGET): $(JNI_HEADERS) $(JNI_SOURCES) - $(CXX) -O3 -Wall -shared -fPIC $(JVM_SHARED_LIBS) -ljvm -lrt $(INCLUDES) $(JNI_SOURCES) -o $(TARGET) - -$(JNI_HEADERS): $(JNI_JAVA_SOURCES) - mkdir -p $(TARGET_DIR)/jni - javah -force -classpath $(JAVAH_CLASSPATH) -d $(WORKING_DIR) $(JAVA_CLASSES) +$(TARGET): $(JNI_SOURCES) + $(CXX) -O3 -Wall -shared -fPIC $(JVM_SHARED_LIBS) $(LRT) $(INCLUDES) $(JNI_SOURCES) -o $(TARGET) clean: rm $(TARGET) diff --git a/affinity/src/main/c/net_openhft_ticker_impl_JNIClock.cpp b/affinity/src/main/c/net_openhft_ticker_impl_JNIClock.cpp index e269308a5..2cf233d3a 100644 --- a/affinity/src/main/c/net_openhft_ticker_impl_JNIClock.cpp +++ b/affinity/src/main/c/net_openhft_ticker_impl_JNIClock.cpp @@ -1,19 +1,6 @@ -/* vim: syntax=cpp - * Copyright 2015 Higher Frequency Trading - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. +/** + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif @@ -53,6 +40,30 @@ unsigned long long rdtsc(){ __asm__ __volatile__("mfspr %%r3, 268": "=r" (rval)); return rval; } +#elif defined(__aarch64__) // ARMv8-A (AArch64) +#include +inline uint64_t rdtsc() { + uint64_t virtual_timer_value; + asm volatile("mrs %0, cntvct_el0" : "=r"(virtual_timer_value)); + return virtual_timer_value; +} +#elif defined(__ARM_ARCH) && (__ARM_ARCH >= 7) // ARMv7-A (32-bit) +#include +inline uint64_t rdtsc() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; +} +#elif defined(__APPLE__) +#include +inline uint64_t rdtsc() { + return mach_absolute_time(); +} +#elif defined(_MSC_VER) +#include +inline uint64_t rdtsc() { + return __rdtsc(); +} #endif /* diff --git a/affinity/src/main/c/software_chronicle_enterprise_internals_impl_NativeAffinity.cpp b/affinity/src/main/c/software_chronicle_enterprise_internals_impl_NativeAffinity.cpp index 0a2384022..f13d566a1 100644 --- a/affinity/src/main/c/software_chronicle_enterprise_internals_impl_NativeAffinity.cpp +++ b/affinity/src/main/c/software_chronicle_enterprise_internals_impl_NativeAffinity.cpp @@ -1,30 +1,19 @@ -/* vim: syntax=cpp - * Copyright 2011 Higher Frequency Trading - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. +/** + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include -#include -#include -#include -#include -#include - +#ifdef __linux__ + #include + #include + #include + #include + #include +#endif +#include #include "software_chronicle_enterprise_internals_impl_NativeAffinity.h" /* @@ -35,6 +24,7 @@ JNIEXPORT jbyteArray JNICALL Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getAffinity0 (JNIEnv *env, jclass c) { +#ifdef __linux__ // The default size of the structure supports 1024 CPUs, should be enough // for now In the future we can use dynamic sets, which can support more // CPUs, given OS can handle them as well @@ -53,6 +43,9 @@ JNIEXPORT jbyteArray JNICALL Java_software_chronicle_enterprise_internals_impl_N env->SetByteArrayRegion(ret, 0, size, bytes); return ret; +#else + throw std::runtime_error("Not supported"); +#endif } /* @@ -63,6 +56,7 @@ JNIEXPORT jbyteArray JNICALL Java_software_chronicle_enterprise_internals_impl_N JNIEXPORT void JNICALL Java_software_chronicle_enterprise_internals_impl_NativeAffinity_setAffinity0 (JNIEnv *env, jclass c, jbyteArray affinity) { +#ifdef __linux__ cpu_set_t mask; const size_t size = sizeof(mask); CPU_ZERO(&mask); @@ -71,6 +65,9 @@ JNIEXPORT void JNICALL Java_software_chronicle_enterprise_internals_impl_NativeA memcpy(&mask, bytes, size); sched_setaffinity(0, size, &mask); +#else + throw std::runtime_error("Not supported"); +#endif } /* @@ -80,7 +77,12 @@ JNIEXPORT void JNICALL Java_software_chronicle_enterprise_internals_impl_NativeA */ JNIEXPORT jint JNICALL Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getProcessId0 (JNIEnv *env, jclass c) { +#ifndef __linux__ + throw std::runtime_error("Not supported"); +#else + return (jint) getpid(); +#endif } /* @@ -90,7 +92,12 @@ JNIEXPORT jint JNICALL Java_software_chronicle_enterprise_internals_impl_NativeA */ JNIEXPORT jint JNICALL Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getThreadId0 (JNIEnv *env, jclass c) { +#ifndef __linux__ + throw std::runtime_error("Not supported"); +#else + return (jint) (pid_t) syscall (SYS_gettid); +#endif } /* @@ -100,6 +107,11 @@ JNIEXPORT jint JNICALL Java_software_chronicle_enterprise_internals_impl_NativeA */ JNIEXPORT jint JNICALL Java_software_chronicle_enterprise_internals_impl_NativeAffinity_getCpu0 (JNIEnv *env, jclass c) { +#ifndef __linux__ + throw std::runtime_error("Not supported"); +#else + return (jint) sched_getcpu(); +#endif } diff --git a/affinity/src/main/c/software_chronicle_enterprise_internals_impl_NativeAffinity_MacOSX.c b/affinity/src/main/c/software_chronicle_enterprise_internals_impl_NativeAffinity_MacOSX.c index a2d4666ce..67dafb1ef 100644 --- a/affinity/src/main/c/software_chronicle_enterprise_internals_impl_NativeAffinity_MacOSX.c +++ b/affinity/src/main/c/software_chronicle_enterprise_internals_impl_NativeAffinity_MacOSX.c @@ -1,19 +1,6 @@ -/* - * Copyright 2011-2012 Peter Lawrey & Jerry Shea - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. +/** + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - #include #include #include diff --git a/affinity/src/main/java/java/lang/ThreadLifecycleListener.java b/affinity/src/main/java/java/lang/ThreadLifecycleListener.java deleted file mode 100644 index f2e4c01e3..000000000 --- a/affinity/src/main/java/java/lang/ThreadLifecycleListener.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package java.lang; - -/** - * A listener for various events in a Thread's life: creation, termination, etc. - */ -public interface ThreadLifecycleListener { - - /** - * The specified thread is about to be started. - * - * @param t the thread which is being started - */ - void started(Thread t); - - /** - * The specified thread failed to start. - * - * @param t the thread that had a failed start - */ - void startFailed(Thread t); - - /** - * The specified thread has been terminated. - * - * @param t the thread that has been terminated - */ - void terminated(Thread t); -} diff --git a/affinity/src/main/java/java/lang/ThreadTrackingGroup.java b/affinity/src/main/java/java/lang/ThreadTrackingGroup.java deleted file mode 100644 index 7a4d6bb35..000000000 --- a/affinity/src/main/java/java/lang/ThreadTrackingGroup.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package java.lang; - -/** - * A wrapper of {@link java.lang.ThreadGroup} that tracks the creation and termination of threads. - */ -public class ThreadTrackingGroup extends ThreadGroup { - - /** - * Listener to be notified of various events in thread lifecycles. - */ - private final ThreadLifecycleListener listener; - - public ThreadTrackingGroup(ThreadGroup parent, ThreadLifecycleListener listener) { - super(parent, ThreadTrackingGroup.class.getSimpleName().toLowerCase() + System.identityHashCode(listener)); - this.listener = listener; - } - - @Override - void add(Thread t) { - // System.out.println("ThreadTrackingGroup.add: " + t); //todo: remove - super.add(t); - listener.started(t); - } - - @Override - void threadStartFailed(Thread t) { - super.threadStartFailed(t); - listener.startFailed(t); - } - - @Override - void threadTerminated(Thread t) { - super.threadTerminated(t); - listener.terminated(t); - } -} diff --git a/affinity/src/main/java/net/openhft/affinity/Affinity.java b/affinity/src/main/java/net/openhft/affinity/Affinity.java index e9025f248..8dadd8a15 100644 --- a/affinity/src/main/java/net/openhft/affinity/Affinity.java +++ b/affinity/src/main/java/net/openhft/affinity/Affinity.java @@ -1,22 +1,9 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; +import com.sun.jna.Native; import net.openhft.affinity.impl.*; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -34,7 +21,7 @@ * @author peter.lawrey */ public enum Affinity { - ; + ; // none static final Logger LOGGER = LoggerFactory.getLogger(Affinity.class); @NotNull private static final IAffinity AFFINITY_IMPL; @@ -128,7 +115,7 @@ private static boolean isMacJNAAffinityUsable() { return true; } else { - LOGGER.warn("MAX OSX JNA-based affinity not usable due to JNA not being available!"); + LOGGER.warn("MAC OSX JNA-based affinity not usable due to JNA not being available!"); return false; } } @@ -187,35 +174,37 @@ public static void setThreadId() { } public static boolean isJNAAvailable() { - if (JNAAvailable == null) - try { - Class.forName("com.sun.jna.Platform"); - JNAAvailable = true; - } catch (ClassNotFoundException ignored) { + if (JNAAvailable == null) { + int majorVersion = Integer.parseInt(Native.VERSION.split("\\.")[0]); + if (majorVersion < 5) { + LOGGER.warn("Affinity library requires JNA version >= 5"); JNAAvailable = false; + } else { + try { + Class.forName("com.sun.jna.Platform"); + JNAAvailable = true; + } catch (ClassNotFoundException ignored) { + JNAAvailable = false; + } } + } return JNAAvailable; } public static AffinityLock acquireLock() { - return isNonForkingAffinityAvailable() ? NonForkingAffinityLock.acquireLock() : AffinityLock.acquireLock(); + return AffinityLock.acquireLock(); } public static AffinityLock acquireCore() { - return isNonForkingAffinityAvailable() ? NonForkingAffinityLock.acquireCore() : AffinityLock.acquireCore(); + return AffinityLock.acquireCore(); } public static AffinityLock acquireLock(boolean bind) { - return isNonForkingAffinityAvailable() ? NonForkingAffinityLock.acquireLock(bind) : AffinityLock.acquireLock(bind); + return AffinityLock.acquireLock(bind); } public static AffinityLock acquireCore(boolean bind) { - return isNonForkingAffinityAvailable() ? NonForkingAffinityLock.acquireCore(bind) : AffinityLock.acquireCore(bind); - } - - private static boolean isNonForkingAffinityAvailable() { - BootClassPath bootClassPath = BootClassPath.INSTANCE; - return bootClassPath.has("java.lang.ThreadTrackingGroup") && bootClassPath.has("java.lang.ThreadLifecycleListener"); + return AffinityLock.acquireCore(bind); } public static void resetToBaseAffinity() { diff --git a/affinity/src/main/java/net/openhft/affinity/AffinityLock.java b/affinity/src/main/java/net/openhft/affinity/AffinityLock.java index fc2eae07c..cf0ed4ca0 100644 --- a/affinity/src/main/java/net/openhft/affinity/AffinityLock.java +++ b/affinity/src/main/java/net/openhft/affinity/AffinityLock.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; import net.openhft.affinity.impl.NoCpuLayout; @@ -69,6 +55,7 @@ public class AffinityLock implements Closeable { * Logical ID of the CPU to which this lock belongs to. */ private final int cpuId; + private final int cpuId2; /** * CPU to which this lock belongs to is of general use. */ @@ -88,9 +75,10 @@ public class AffinityLock implements Closeable { Throwable boundHere; private boolean resetAffinity = true; - AffinityLock(int cpuId, boolean base, boolean reservable, LockInventory lockInventory) { + AffinityLock(int cpuId, int cpuId2, boolean base, boolean reservable, LockInventory lockInventory) { this.lockInventory = lockInventory; this.cpuId = cpuId; + this.cpuId2 = cpuId2; this.base = base; this.reservable = reservable; } @@ -121,7 +109,6 @@ private static BitSet getReservedAffinity0() { reserverable.set(1, PROCESSORS, true); reserverable.andNot(BASE_AFFINITY); if (reserverable.isEmpty() && PROCESSORS > 1) { - LoggerFactory.getLogger(AffinityLock.class).info("No isolated CPUs found, so assuming CPUs 1 to {} available.", (PROCESSORS - 1)); // make all but first CPUs available reserverable.set(1, PROCESSORS); return reserverable; @@ -132,9 +119,9 @@ private static BitSet getReservedAffinity0() { reservedAffinity = reservedAffinity.trim(); long[] longs = new long[1 + (reservedAffinity.length() - 1) / 16]; int end = reservedAffinity.length(); - for(int i = 0; i < longs.length ; i++) { + for (int i = 0; i < longs.length; i++) { int begin = Math.max(0, end - 16); - longs[i] = Long.parseLong(reservedAffinity.substring(begin, end), 16); + longs[i] = Long.parseUnsignedLong(reservedAffinity.substring(begin, end), 16); end = begin; } return BitSet.valueOf(longs); @@ -149,6 +136,14 @@ public static AffinityLock acquireLock() { return acquireLock(true); } + static class Warnings { + static void warmNoReservedCPUs() { + if (RESERVED_AFFINITY.isEmpty() && PROCESSORS > 1) { + LoggerFactory.getLogger(AffinityLock.class).info("No isolated CPUs found, so assuming CPUs 1 to {} available.", (PROCESSORS - 1)); + } + } + } + /** * Assign any free core to this thread.

In reality, only one cpu is assigned, the rest of * the threads for that core are reservable so they are not used. @@ -176,12 +171,22 @@ public static AffinityLock acquireLock(boolean bind) { * for defining your thread layout centrally and passing the handle via dependency injection. * * @param cpuId the CPU id to bind to - * @return A handle for an affinity lock. + * @return A handle for an affinity lock, or no lock if no available CPU in the array */ public static AffinityLock acquireLock(int cpuId) { + if (isInvalidCpuId(cpuId)) + return LOCK_INVENTORY.noLock(); return acquireLock(true, cpuId, AffinityStrategies.ANY); } + private static boolean isInvalidCpuId(int cpuId) { + if (cpuId < 0 || cpuId >= PROCESSORS) { + LOGGER.warn("cpuId must be between 0 and {}: {}", PROCESSORS - 1, cpuId); + return true; + } + return false; + } + /** * Assign a cpu which can be bound to the current thread or another thread * Caller passes in an explicit set of preferred CPUs @@ -192,17 +197,16 @@ public static AffinityLock acquireLock(int cpuId) { * @return A handle for an affinity lock, or nolock if no available CPU in the array */ public static AffinityLock acquireLock(int[] cpus) { - for( int cpu : cpus ) - { + for (int cpu : cpus) { + if (isInvalidCpuId(cpu)) continue; AffinityLock lock = tryAcquireLock(true, cpu); - if(lock != null) - { + if (lock != null) { LOGGER.info("Acquired lock on CPU {}", cpu); return lock; } } - LOGGER.warn("Failed to lock any CPU in explicit list " + Arrays.toString(cpus)); + LOGGER.warn("Failed to lock any CPU in explicit list {}", Arrays.toString(cpus)); return LOCK_INVENTORY.noLock(); } @@ -221,7 +225,7 @@ public static AffinityLock acquireLockLastMinus(int n) { *

    *
  • "N" being a positive integer means allocate this CPU,
  • *
  • "last" or "last-N" means allocate from the end,
  • - *
  • "csv:1,2,5,6 eg means allocate first free core from the provided
  • + *
  • "csv:1,2,5,6" eg means allocate first free core from the provided
  • *
  • "any" means allow any
  • *
  • "none" or null means
  • *
  • "0" is not allowed
  • @@ -254,7 +258,7 @@ public static AffinityLock acquireLock(String desc) { } else if (desc.startsWith("csv:")) { String content = desc.substring(4); - int[] cpus = Arrays.asList(content.split(",")).stream() + int[] cpus = Arrays.stream(content.split(",")) .map(String::trim) .mapToInt(Integer::parseInt).toArray(); @@ -274,7 +278,7 @@ public static AffinityLock acquireLock(String desc) { } } if (cpuId <= 0) { - System.err.println("Cannot allocate 0 or negative cpuIds '" + desc + "'"); + LOGGER.warn("Cannot allocate 0 or negative cpuIds '{}'", desc); return LOCK_INVENTORY.noLock(); } return acquireLock(cpuId); @@ -294,6 +298,7 @@ public static AffinityLock acquireCore(boolean bind) { } private static AffinityLock acquireLock(boolean bind, int cpuId, @NotNull AffinityStrategy... strategies) { + Warnings.warmNoReservedCPUs(); return LOCK_INVENTORY.acquireLock(bind, cpuId, strategies); } @@ -301,7 +306,7 @@ private static AffinityLock acquireLock(boolean bind, int cpuId, @NotNull Affini * Try to acquire a lock on the specified core * Returns lock if successful, or null if cpu cannot be acquired * - * @param bind - if true, bind the current thread; if false, reserve a cpu which can be bound later + * @param bind - if true, bind the current thread; if false, reserve a cpu which can be bound later * @param cpuId - the cpu to lock * @return - A handle to an affinity lock on success; null if failed to lock */ @@ -310,6 +315,7 @@ private static AffinityLock tryAcquireLock(boolean bind, int cpuId) { } private static AffinityLock acquireCore(boolean bind, int cpuId, @NotNull AffinityStrategy... strategies) { + Warnings.warmNoReservedCPUs(); return LOCK_INVENTORY.acquireCore(bind, cpuId, strategies); } @@ -323,7 +329,9 @@ public static String dumpLocks() { private static boolean areAssertionsEnabled() { boolean debug = false; + //noinspection AssertWithSideEffects assert debug = true; + //noinspection ConstantValue return debug; } @@ -437,10 +445,11 @@ public void close() { release(); } + @SuppressWarnings({"deprecation", "removal"}) @Override protected void finalize() throws Throwable { if (bound) { - LOGGER.warn("Affinity lock for " + assignedThread + " was discarded rather than release()d in a controlled manner.", boundHere); + LOGGER.warn("Affinity lock for {} was discarded rather than release()d in a controlled manner.", assignedThread, boundHere); release(); } super.finalize(); @@ -453,6 +462,10 @@ public int cpuId() { return cpuId; } + public int cpuId2() { + return cpuId2; + } + /** * @return Was a cpu found to bind this lock to. */ diff --git a/affinity/src/main/java/net/openhft/affinity/AffinityStrategies.java b/affinity/src/main/java/net/openhft/affinity/AffinityStrategies.java index 9a0d3e279..65ee0cbf9 100644 --- a/affinity/src/main/java/net/openhft/affinity/AffinityStrategies.java +++ b/affinity/src/main/java/net/openhft/affinity/AffinityStrategies.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; /** diff --git a/affinity/src/main/java/net/openhft/affinity/AffinityStrategy.java b/affinity/src/main/java/net/openhft/affinity/AffinityStrategy.java index 0d8dc27bd..400c6d3c6 100644 --- a/affinity/src/main/java/net/openhft/affinity/AffinityStrategy.java +++ b/affinity/src/main/java/net/openhft/affinity/AffinityStrategy.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; /** diff --git a/affinity/src/main/java/net/openhft/affinity/AffinitySupport.java b/affinity/src/main/java/net/openhft/affinity/AffinitySupport.java deleted file mode 100644 index d4cc6e249..000000000 --- a/affinity/src/main/java/net/openhft/affinity/AffinitySupport.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package net.openhft.affinity; - -/** - * For backward compatibility with Affinity 2.x - */ -@Deprecated(/* to be removed in x.22 */) -public class AffinitySupport { - - public static int getThreadId() { - return Affinity.getThreadId(); - } - - public static void setThreadId() { - Affinity.setThreadId(); - } -} diff --git a/affinity/src/main/java/net/openhft/affinity/AffinityThreadFactory.java b/affinity/src/main/java/net/openhft/affinity/AffinityThreadFactory.java index a1b15f372..6e1d67e5a 100644 --- a/affinity/src/main/java/net/openhft/affinity/AffinityThreadFactory.java +++ b/affinity/src/main/java/net/openhft/affinity/AffinityThreadFactory.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; import org.jetbrains.annotations.NotNull; @@ -53,12 +39,11 @@ public AffinityThreadFactory(String name, boolean daemon, @NotNull AffinityStrat public synchronized Thread newThread(@NotNull final Runnable r) { String name2 = id <= 1 ? name : (name + '-' + id); id++; - Thread t = new Thread(new Runnable() { - @Override - public void run() { - try (AffinityLock ignored = acquireLockBasedOnLast()) { - r.run(); - } + Thread t = new Thread(() -> { + try (AffinityLock ignored = acquireLockBasedOnLast()) { + //noinspection ConstantValue + assert ignored != null; + r.run(); } }, name2); t.setDaemon(daemon); @@ -66,7 +51,8 @@ public void run() { } private synchronized AffinityLock acquireLockBasedOnLast() { - AffinityLock al = lastAffinityLock == null ? AffinityLock.acquireLock() : lastAffinityLock.acquireLock(strategies); + AffinityLock al = lastAffinityLock == null ? AffinityLock.acquireLock(false) : lastAffinityLock.acquireLock(strategies); + al.bind(); if (al.cpuId() >= 0) lastAffinityLock = al; return al; diff --git a/affinity/src/main/java/net/openhft/affinity/BootClassPath.java b/affinity/src/main/java/net/openhft/affinity/BootClassPath.java index 383e6576f..85efa3279 100644 --- a/affinity/src/main/java/net/openhft/affinity/BootClassPath.java +++ b/affinity/src/main/java/net/openhft/affinity/BootClassPath.java @@ -1,26 +1,15 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; import java.io.IOException; +import java.net.URI; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collections; @@ -38,21 +27,56 @@ enum BootClassPath { private static Set getResourcesOnBootClasspath() { final Logger logger = LoggerFactory.getLogger(BootClassPath.class); final Set resources = new HashSet<>(); + final String bootClassPath = System.getProperty("sun.boot.class.path", ""); - logger.trace("Boot class-path is: {}", bootClassPath); + if (!bootClassPath.isEmpty()) { + logger.trace("Boot class-path is: {}", bootClassPath); - final String pathSeparator = System.getProperty("path.separator"); - logger.trace("Path separator is: '{}'", pathSeparator); + final String pathSeparator = File.pathSeparator; + logger.trace("Path separator is: '{}'", pathSeparator); - final String[] pathElements = bootClassPath.split(pathSeparator); + final String[] pathElements = bootClassPath.split(pathSeparator); - for (final String pathElement : pathElements) { - resources.addAll(findResources(Paths.get(pathElement), logger)); + for (final String pathElement : pathElements) { + resources.addAll(findResources(Paths.get(pathElement), logger)); + } + } else { + resources.addAll(findResourcesInJrt(logger)); } return resources; } + private static Set findResourcesInJrt(final Logger logger) { + final Set jrtResources = new HashSet<>(); + try { + FileSystem fs; + try { + fs = FileSystems.getFileSystem(URI.create("jrt:/")); + } catch (FileSystemNotFoundException | ProviderNotFoundException e) { + fs = FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap()); + } + final Path modules = fs.getPath("/modules"); + Files.walkFileTree(modules, new SimpleFileVisitor() { + @Override + public @NotNull FileVisitResult visitFile(final @NotNull Path file, + final @NotNull BasicFileAttributes attrs) throws IOException { + if (file.getFileName().toString().endsWith(".class")) { + Path relative = modules.relativize(file); + if (relative.getNameCount() > 1) { + Path classPath = relative.subpath(1, relative.getNameCount()); + jrtResources.add(classPath.toString()); + } + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + logger.warn("Error walking jrt filesystem", e); + } + return jrtResources; + } + private static Set findResources(final Path path, final Logger logger) { if (!Files.exists(path)) { return Collections.emptySet(); @@ -67,8 +91,7 @@ private static Set findResources(final Path path, final Logger logger) { private static Set findResourcesInJar(final Path path, final Logger logger) { final Set jarResources = new HashSet<>(); - try { - final JarFile jarFile = new JarFile(path.toFile()); + try (final JarFile jarFile = new JarFile(path.toFile())) { final Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { final JarEntry jarEntry = entries.nextElement(); @@ -88,7 +111,7 @@ private static Set findResourcesInDirectory(final Path path, final Logge try { Files.walkFileTree(path, new SimpleFileVisitor() { @Override - public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { + public @NotNull FileVisitResult visitFile(final @NotNull Path file, final @NotNull BasicFileAttributes attrs) throws IOException { if (file.getFileName().toString().endsWith(".class")) { dirResources.add(path.relativize(file).toString()); } @@ -96,7 +119,7 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr } }); } catch (IOException e) { - logger.warn("Error walking dir: " + path, e); + logger.warn("Error walking dir: {}", path, e); } return dirResources; diff --git a/affinity/src/main/java/net/openhft/affinity/CpuLayout.java b/affinity/src/main/java/net/openhft/affinity/CpuLayout.java index 92981330e..0cbdd6412 100644 --- a/affinity/src/main/java/net/openhft/affinity/CpuLayout.java +++ b/affinity/src/main/java/net/openhft/affinity/CpuLayout.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; /** @@ -49,4 +35,10 @@ public interface CpuLayout { * @return which thread on a core this cpu is on. */ int threadId(int cpuId); + + /** + * @param cpuId the logical processor number + * @return the hyperthreaded pair number or 0 if not hyperthreaded. + */ + int pair(int cpuId); } diff --git a/affinity/src/main/java/net/openhft/affinity/IAffinity.java b/affinity/src/main/java/net/openhft/affinity/IAffinity.java index f68598e08..c473a5400 100644 --- a/affinity/src/main/java/net/openhft/affinity/IAffinity.java +++ b/affinity/src/main/java/net/openhft/affinity/IAffinity.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; import java.util.BitSet; diff --git a/affinity/src/main/java/net/openhft/affinity/LockCheck.java b/affinity/src/main/java/net/openhft/affinity/LockCheck.java index 2fe67ad8c..e3c485fed 100644 --- a/affinity/src/main/java/net/openhft/affinity/LockCheck.java +++ b/affinity/src/main/java/net/openhft/affinity/LockCheck.java @@ -1,22 +1,9 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; +import net.openhft.affinity.impl.Utilities; import net.openhft.affinity.lockchecker.FileLockBasedLockChecker; import net.openhft.affinity.lockchecker.LockChecker; import org.slf4j.Logger; @@ -28,8 +15,8 @@ /** * @author Rob Austin. */ -enum LockCheck { - ; +public enum LockCheck { + ; // none private static final Logger LOGGER = LoggerFactory.getLogger(LockCheck.class); private static final String OS = System.getProperty("os.name").toLowerCase(); @@ -38,10 +25,8 @@ enum LockCheck { private static final LockChecker lockChecker = FileLockBasedLockChecker.getInstance(); - static long getPID() { - String processName = - java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); - return Long.parseLong(processName.split("@")[0]); + public static long getPID() { + return Utilities.currentProcessId(); } static boolean canOSSupportOperation() { @@ -52,30 +37,14 @@ public static boolean isCpuFree(int cpu) { if (!canOSSupportOperation()) return true; - if (isLockFree(cpu)) { - return true; - } else { - int currentProcess = 0; - try { - currentProcess = getProcessForCpu(cpu); - } catch (RuntimeException | IOException e) { - LOGGER.warn("Failed to determine process on cpu " + cpu, e); - e.printStackTrace(); - return true; - } - if (!isProcessRunning(currentProcess)) { - lockChecker.releaseLock(cpu); - return true; - } - return false; - } + return isLockFree(cpu); } - static void replacePid(int cpu, long processID) throws IOException { - storePid(processID, cpu); + static boolean replacePid(int cpu, int cpu2, long processID) throws IOException { + return storePid(processID, cpu, cpu2); } - static boolean isProcessRunning(long pid) { + public static boolean isProcessRunning(long pid) { if (canOSSupportOperation()) return new File("/proc/" + pid).exists(); else @@ -86,17 +55,18 @@ static boolean isProcessRunning(long pid) { * stores the pid in a file, named by the core, the pid is written to the file with the date * below */ - private synchronized static void storePid(long processID, int cpu) throws IOException { - if (!lockChecker.obtainLock(cpu, Long.toString(processID))) { - throw new IOException(String.format("Cannot obtain file lock for cpu %d", cpu)); - } + private synchronized static boolean storePid(long processID, int cpu, int cpu2) throws IOException { + return lockChecker.obtainLock(cpu, cpu2, Long.toString(processID)); } private synchronized static boolean isLockFree(int id) { return lockChecker.isLockFree(id); } - static int getProcessForCpu(int core) throws IOException { + public static int getProcessForCpu(int core) throws IOException { + if (!canOSSupportOperation()) + return EMPTY_PID; + String meta = lockChecker.getMetaInfo(core); if (meta != null && !meta.isEmpty()) { @@ -109,18 +79,13 @@ static int getProcessForCpu(int core) throws IOException { return EMPTY_PID; } - static void updateCpu(int cpu) { + static boolean updateCpu(int cpu, int cpu2) throws IOException { if (!canOSSupportOperation()) - return; - try { - replacePid(cpu, getPID()); - } catch (IOException e) { - LOGGER.warn("Failed to update lock file for cpu " + cpu, e); - e.printStackTrace(); - } + return true; + return replacePid(cpu, cpu2, getPID()); } public static void releaseLock(int cpu) { lockChecker.releaseLock(cpu); } -} \ No newline at end of file +} diff --git a/affinity/src/main/java/net/openhft/affinity/LockInventory.java b/affinity/src/main/java/net/openhft/affinity/LockInventory.java index 7523e5e28..b54615219 100644 --- a/affinity/src/main/java/net/openhft/affinity/LockInventory.java +++ b/affinity/src/main/java/net/openhft/affinity/LockInventory.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; import net.openhft.affinity.impl.NullAffinity; @@ -22,6 +8,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.channels.ClosedByInterruptException; +import java.util.Map; import java.util.NavigableMap; import java.util.TreeMap; @@ -50,7 +39,7 @@ public static String dumpLocks(@NotNull AffinityLock[] locks) { for (int i = 0; i < locks.length; i++) { AffinityLock al = locks[i]; sb.append(i).append(": "); - sb.append(al.toString()); + sb.append(al); sb.append('\n'); } return sb.toString(); @@ -69,9 +58,27 @@ private static boolean isAnyCpu(final int cpuId) { return cpuId == AffinityLock.ANY_CPU; } - private static void updateLockForCurrentThread(final boolean bind, final AffinityLock al, final boolean b) { - al.assignCurrentThread(bind, b); - LockCheck.updateCpu(al.cpuId()); + /** + * Update the lock for the current thread + * + * @param bind Whether to also bind the thread to the core + * @param al The lock to update + * @param wholeCore Whether to bind the whole core + * @return true if the lock was acquired, false otherwise + */ + private static boolean updateLockForCurrentThread(final boolean bind, final AffinityLock al, final boolean wholeCore) throws ClosedByInterruptException { + try { + if (LockCheck.updateCpu(al.cpuId(), wholeCore ? al.cpuId2() : 0)) { + al.assignCurrentThread(bind, wholeCore); + return true; + } + } catch (ClosedByInterruptException e) { + throw e; + + } catch (IOException e) { + LOGGER.info("Error occurred acquiring lock, trying another {}", String.valueOf(e)); + } + return false; } public final synchronized CpuLayout getCpuLayout() { @@ -87,7 +94,9 @@ public final synchronized void set(CpuLayout cpuLayout) { final boolean base = AffinityLock.BASE_AFFINITY.get(i); final boolean reservable = AffinityLock.RESERVED_AFFINITY.get(i); LOGGER.trace("cpu {} base={} reservable= {}", i, base, reservable); - AffinityLock lock = logicalCoreLocks[i] = newLock(i, base, reservable); + assert logicalCoreLocks != null; + @SuppressWarnings("resource") + AffinityLock lock = logicalCoreLocks[i] = newLock(i, cpuLayout.pair(i), base, reservable); int layoutId = lock.cpuId(); int physicalCore = toPhysicalCore(layoutId); @@ -97,6 +106,24 @@ public final synchronized void set(CpuLayout cpuLayout) { } locks[cpuLayout.threadId(layoutId)] = lock; } + shrink(physicalCoreLocks); + } + + /** + * If some CPUs are hyper-threaded, but not others, fix up the HT CPUs + */ + private void shrink(NavigableMap physicalCoreLocks) { + for (Map.Entry e : physicalCoreLocks.entrySet()) { + final AffinityLock[] locks = e.getValue(); + for (int i = 0; i < locks.length; i++) { + if (locks[i] == null) { + final AffinityLock[] locks2 = new AffinityLock[i]; + System.arraycopy(locks, 0, locks2, 0, i); + physicalCoreLocks.put(e.getKey(), locks2); + break; + } + } + } } public final synchronized AffinityLock acquireLock(boolean bind, int cpuId, AffinityStrategy... strategies) { @@ -104,26 +131,39 @@ public final synchronized AffinityLock acquireLock(boolean bind, int cpuId, Affi return noLock(); final boolean specificCpuRequested = !isAnyCpu(cpuId); - if (specificCpuRequested && cpuId != 0) { - final AffinityLock required = logicalCoreLocks[cpuId]; - if (required.canReserve(true) && anyStrategyMatches(cpuId, cpuId, strategies)) { - updateLockForCurrentThread(bind, required, false); - return required; + try { + if (specificCpuRequested && cpuId != 0) { + if (cpuId > logicalCoreLocks.length) { + LOGGER.warn("Unable to acquire lock on CPU {} for thread {}, as not enough CPUs", + cpuId, Thread.currentThread()); + return noLock(); + } + + final AffinityLock required = logicalCoreLocks[cpuId]; + if (required.canReserve(true) + && anyStrategyMatches(cpuId, cpuId, strategies) + && updateLockForCurrentThread(bind, required, false)) { + return required; + } + LOGGER.warn("Unable to acquire lock on CPU {} for thread {}, trying to find another CPU", + cpuId, Thread.currentThread()); } - LOGGER.warn("Unable to acquire lock on CPU {} for thread {}, trying to find another CPU", - cpuId, Thread.currentThread()); - } - for (AffinityStrategy strategy : strategies) { - // consider all processors except cpu 0 which is usually used by the OS. - // if you have only one core, this library is not appropriate in any case. - for (int i = logicalCoreLocks.length - 1; i > 0; i--) { - AffinityLock al = logicalCoreLocks[i]; - if (al.canReserve(false) && (isAnyCpu(cpuId) || strategy.matches(cpuId, al.cpuId()))) { - updateLockForCurrentThread(bind, al, false); - return al; + for (AffinityStrategy strategy : strategies) { + // consider all processors except cpu 0 which is usually used by the OS. + // if you have only one core, this library is not appropriate in any case. + for (int i = logicalCoreLocks.length - 1; i > 0; i--) { + AffinityLock al = logicalCoreLocks[i]; + if (al.canReserve(false) + && (isAnyCpu(cpuId) || strategy.matches(cpuId, al.cpuId())) + && updateLockForCurrentThread(bind, al, false)) { + return al; + } } } + } catch (ClosedByInterruptException e) { + Thread.currentThread().interrupt(); + return noLock(); } LOGGER.warn("No reservable CPU for {}", Thread.currentThread()); @@ -134,11 +174,17 @@ public final synchronized AffinityLock acquireLock(boolean bind, int cpuId, Affi public final synchronized AffinityLock tryAcquireLock(boolean bind, int cpuId) { if (getAffinityImpl() instanceof NullAffinity) return null; - + if (cpuId > logicalCoreLocks.length) + return null; final AffinityLock required = logicalCoreLocks[cpuId]; - if (required.canReserve(true)) { - updateLockForCurrentThread(bind, required, false); - return required; + try { + if (required.canReserve(true) + && updateLockForCurrentThread(bind, required, false)) { + return required; + } + } catch (ClosedByInterruptException e) { + Thread.currentThread().interrupt(); + return noLock(); } LOGGER.warn("Unable to acquire lock on CPU {} for thread {}, trying to find another CPU", @@ -156,8 +202,14 @@ public final synchronized AffinityLock acquireCore(boolean bind, int cpuId, Affi continue LOOP; final AffinityLock al = als[0]; - updateLockForCurrentThread(bind, al, true); - return al; + try { + if (updateLockForCurrentThread(bind, al, true)) { + return al; + } + } catch (ClosedByInterruptException e) { + Thread.currentThread().interrupt(); + return noLock(); + } } } @@ -213,8 +265,8 @@ public final synchronized String dumpLocks() { return dumpLocks(logicalCoreLocks); } - protected AffinityLock newLock(int cpuId, boolean base, boolean reservable) { - return new AffinityLock(cpuId, base, reservable, this); + protected AffinityLock newLock(int cpuId, int cpuId2, boolean base, boolean reservable) { + return new AffinityLock(cpuId, cpuId2, base, reservable, this); } private void reset(CpuLayout cpuLayout) { @@ -237,6 +289,6 @@ private void releaseAffinityLock(final Thread t, final AffinityLock al, final St } public AffinityLock noLock() { - return newLock(AffinityLock.ANY_CPU, false, false); + return newLock(AffinityLock.ANY_CPU, 0, false, false); } } diff --git a/affinity/src/main/java/net/openhft/affinity/MicroJitterSampler.java b/affinity/src/main/java/net/openhft/affinity/MicroJitterSampler.java index dc4ac4a91..30e27d221 100644 --- a/affinity/src/main/java/net/openhft/affinity/MicroJitterSampler.java +++ b/affinity/src/main/java/net/openhft/affinity/MicroJitterSampler.java @@ -1,70 +1,9 @@ /* - * Copyright 2014 Higher Frequency Trading - * - * http://chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; import java.io.PrintStream; -/* e.g. -Windows 10 i7-4770 laptop -After 1845 seconds, the average per hour was -2us 2435969 -3us 548812 -4us 508041 -6us 60320 -8us 25374 -10us 1832324 -14us 2089216 -20us 391901 -30us 16063 -40us 6440 -60us 2617 -80us 1487 -100us 1241 -140us 826 -200us 2108 -300us 601 -400us 159 -600us 129 -800us 215 -1ms 155 -2ms 229 -5ms 24 -10ms 38 -20ms 32 - -On an Centos 7 machine with an isolated CPU. -After 2145 seconds, the average per hour was -2us 781271 -3us 1212123 -4us 13504 -6us 489 -8us 2 -10us 3032577 -14us 17475 -20us 628 -30us 645 -40us 1301 -60us 1217 -80us 1306 -100us 1526 -140us 22 - - */ /** * User: peter.lawrey Date: 30/06/13 Time: 13:13 @@ -80,41 +19,61 @@ public class MicroJitterSampler { }; private static final double UTIL = Double.parseDouble(System.getProperty("util", "50")); private static final boolean BUSYWAIT = Boolean.parseBoolean(System.getProperty("busywait", "false")); + private static final String CPU = System.getProperty("cpu", "none"); - // static final int CPU = Integer.getInteger("cpu", 0); private final int[] count = new int[DELAY.length]; private long totalTime = 0; - private static void pause() throws InterruptedException - { - if(BUSYWAIT) { + private static void pause() throws InterruptedException { + if (BUSYWAIT) { long now = System.nanoTime(); - while(System.nanoTime() - now < 1_000_000); + //noinspection StatementWithEmptyBody + while (System.nanoTime() - now < 1_000_000) ; } else { Thread.sleep(1); } - } + public static void main(String... ignored) throws InterruptedException { - // AffinityLock al = AffinityLock.acquireLock(); - - // warmup. - new MicroJitterSampler().sample(1000 * 1000 * 1000); - - MicroJitterSampler microJitterSampler = new MicroJitterSampler(); - while (!Thread.currentThread().isInterrupted()) { - if (UTIL >= 100) { - microJitterSampler.sample(30L * 1000 * 1000 * 1000); - } else { - long sampleLength = (long) ((1 / (1 - UTIL / 100) - 1) * 1000 * 1000); - for (int i = 0; i < 30 * 1000; i += 2) { - microJitterSampler.sample(sampleLength); - //noinspection BusyWait - pause(); - } + MicroJitterSampler sampler = new MicroJitterSampler(); + + Thread t = new Thread(sampler::run); + t.start(); + t.join(); + } + + private void once() throws InterruptedException { + if (UTIL >= 100) { + sample(30L * 1000 * 1000 * 1000); + } else { + long sampleLength = (long) ((1 / (1 - UTIL / 100) - 1) * 1000 * 1000); + for (int i = 0; i < 30 * 1000; i += 2) { + sample(sampleLength); + //noinspection BusyWait + pause(); } + } + } - microJitterSampler.print(System.out); + public void run() { + try (final AffinityLock lock = AffinityLock.acquireLock(CPU)) { + assert lock != null; + boolean first = true; + System.out.println("Warming up..."); + while (!Thread.currentThread().isInterrupted()) { + once(); + + if (first) { + reset(); + first = false; + System.out.println("Warmup complete. Running jitter tests..."); + continue; + } + + print(System.out); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } } @@ -125,6 +84,12 @@ private static String asString(long timeNS) { timeNS / 1000000000 + "sec"; } + void reset() { + for (int i = 0; i < DELAY.length; ++i) + count[i] = 0; + totalTime = 0; + } + void sample(long intervalNS) { long prev = System.nanoTime(); long end = prev + intervalNS; @@ -154,3 +119,83 @@ void print(PrintStream ps) { ps.println(); } } +/* e.g. +Ubuntu 20.04, Ryzen 5950X with an isolated CPU. (init 3) sudo cpupower -c {cpu} -g performance, run from command line +After 3600 seconds, the average per hour was +2us 2571 +3us 2304 +4us 376 +6us 1 +10us 1 +20us 1 +30us 1 +40us 2 +60us 1 + +Ubuntu 20.04, Ryzen 5950X with an isolated CPU. (init 5) sudo cpupower -c {cpu} -g performance, run from command line +After 3600 seconds, the average per hour was +2us 2157 +3us 3444 +4us 3654 +6us 135 +8us 4 +14us 1 +20us 1 +40us 2 +60us 1 + +Ubuntu 20.04, Ryzen 5950X with an isolated CPU. (init 5) sudo cpupower -c {cpu} -g performance, run from IntelliJ CE +After 7200 seconds, the average per hour was +2us 2189 +3us 3341 +4us 2335 +6us 191 +8us 4 +14us 1 +20us 1 + +Windows 10 i7-4770 laptop +After 1845 seconds, the average per hour was +2us 2435969 +3us 548812 +4us 508041 +6us 60320 +8us 25374 +10us 1832324 +14us 2089216 +20us 391901 +30us 16063 +40us 6440 +60us 2617 +80us 1487 +100us 1241 +140us 826 +200us 2108 +300us 601 +400us 159 +600us 129 +800us 215 +1ms 155 +2ms 229 +5ms 24 +10ms 38 +20ms 32 + +On an Centos 7 machine with an isolated CPU. +After 2145 seconds, the average per hour was +2us 781271 +3us 1212123 +4us 13504 +6us 489 +8us 2 +10us 3032577 +14us 17475 +20us 628 +30us 645 +40us 1301 +60us 1217 +80us 1306 +100us 1526 +140us 22 + + */ diff --git a/affinity/src/main/java/net/openhft/affinity/NonForkingAffinityLock.java b/affinity/src/main/java/net/openhft/affinity/NonForkingAffinityLock.java deleted file mode 100644 index d6e3440f2..000000000 --- a/affinity/src/main/java/net/openhft/affinity/NonForkingAffinityLock.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package net.openhft.affinity; - -import net.openhft.affinity.impl.NoCpuLayout; -import org.jetbrains.annotations.NotNull; - -import java.lang.reflect.Field; - -public class NonForkingAffinityLock extends AffinityLock implements ThreadLifecycleListener { - - private static final Field GROUP_FIELD = makeThreadFieldModifiable("group"); - - private static final Field TARGET_FIELD = makeThreadFieldModifiable("target"); - - private static final LockInventory LOCK_INVENTORY = new LockInventory(new NoCpuLayout(PROCESSORS)) { - @Override - protected AffinityLock newLock(int cpuId, boolean base, boolean reservable) { - return new NonForkingAffinityLock(cpuId, base, reservable, this); - } - }; - - NonForkingAffinityLock(int cpuId, boolean base, boolean reservable, LockInventory lockInventory) { - super(cpuId, base, reservable, lockInventory); - } - - /** - * Assign any free cpu to this thread. - * - * @return A handle for the current AffinityLock. - */ - public static AffinityLock acquireLock() { - return acquireLock(true); - } - - /** - * Assign any free core to this thread. - *

    - * In reality, only one cpu is assigned, the rest of the threads for that core are reservable so they are not used. - * - * @return A handle for the current AffinityLock. - */ - public static AffinityLock acquireCore() { - return acquireCore(true); - } - - /** - * Assign a cpu which can be bound to the current thread or another thread. - *

    - * This can be used for defining your thread layout centrally and passing the handle via dependency injection. - * - * @param bind if true, bind the current thread, if false, reserve a cpu which can be bound later. - * @return A handle for an affinity lock. - */ - public static AffinityLock acquireLock(boolean bind) { - return acquireLock(bind, AffinityLock.ANY_CPU, AffinityStrategies.ANY); - } - - /** - * Assign a core(and all its cpus) which can be bound to the current thread or another thread. - *

    - * This can be used for defining your thread layout centrally and passing the handle via dependency injection. - * - * @param bind if true, bind the current thread, if false, reserve a cpu which can be bound later. - * @return A handle for an affinity lock. - */ - public static AffinityLock acquireCore(boolean bind) { - return acquireCore(bind, AffinityLock.ANY_CPU, AffinityStrategies.ANY); - } - - private static AffinityLock acquireLock(boolean bind, int cpuId, @NotNull AffinityStrategy... strategies) { - return LOCK_INVENTORY.acquireLock(bind, cpuId, strategies); - } - - private static AffinityLock acquireCore(boolean bind, int cpuId, @NotNull AffinityStrategy... strategies) { - return LOCK_INVENTORY.acquireCore(bind, cpuId, strategies); - } - - /** - * Set the CPU layout for this machine. CPUs which are not mentioned will be ignored. - *

    - * Changing the layout will have no impact on thread which have already been assigned. - * It only affects subsequent assignments. - * - * @param cpuLayout for this application to use for this machine. - */ - public static void cpuLayout(@NotNull CpuLayout cpuLayout) { - LOCK_INVENTORY.set(cpuLayout); - } - - /** - * @return The current CpuLayout for the application. - */ - @NotNull - public static CpuLayout cpuLayout() { - return LOCK_INVENTORY.getCpuLayout(); - } - - /** - * @return All the current locks as a String. - */ - @NotNull - public static String dumpLocks() { - return LOCK_INVENTORY.dumpLocks(); - } - - private static Field makeThreadFieldModifiable(String fieldName) { - try { - Field field = Thread.class.getDeclaredField(fieldName); - field.setAccessible(true); - return field; - } catch (NoSuchFieldException e) { - throw new RuntimeException(Thread.class.getName() + " class doesn't have a " + fieldName + " field! Quite unexpected!"); - } - } - - private static void changeGroupOfThread(Thread thread, ThreadGroup group) { - try { - GROUP_FIELD.set(thread, group); - } catch (IllegalAccessException e) { - throw new RuntimeException("Failed changing " + Thread.class.getName() + "'s the '" + GROUP_FIELD.getName() + "' field! Reason: " + e.getMessage()); - } - } - - private static void wrapRunnableOfThread(Thread thread, final AffinityLock lock) { - try { - final Runnable originalRunnable = (Runnable) TARGET_FIELD.get(thread); - TARGET_FIELD.set( - thread, - new Runnable() { - @Override - public void run() { - lock.release(); - originalRunnable.run(); - } - } - ); - } catch (IllegalAccessException e) { - throw new RuntimeException("Failed wrapping " + Thread.class.getName() + "'s '" + TARGET_FIELD.getName() + "' field! Reason: " + e.getMessage()); - } - } - - @Override - public void bind(boolean wholeCore) { - super.bind(wholeCore); - Thread thread = Thread.currentThread(); - changeGroupOfThread(thread, new ThreadTrackingGroup(thread.getThreadGroup(), this)); - } - - @Override - public void release() { - Thread thread = Thread.currentThread(); - changeGroupOfThread(thread, thread.getThreadGroup().getParent()); - super.release(); - } - - @Override - public void started(Thread t) { - wrapRunnableOfThread(t, this); - } - - @Override - public void startFailed(Thread t) { - } - - @Override - public void terminated(Thread t) { - } -} diff --git a/affinity/src/main/java/net/openhft/affinity/impl/LinuxHelper.java b/affinity/src/main/java/net/openhft/affinity/impl/LinuxHelper.java index deee9a908..a52404bba 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/LinuxHelper.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/LinuxHelper.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; import com.sun.jna.*; @@ -41,7 +27,7 @@ public class LinuxHelper { ver = new VersionHelper(uname.getRealeaseVersion()); } } catch (Throwable e) { - //logger.warn("Failed to determine Linux version: " + e); + //Jvm.warn().on(getClass(), "Failed to determine Linux version: " + e); } version = ver; @@ -66,10 +52,10 @@ cpu_set_t sched_getaffinity() { return cpuset; } - public static void sched_setaffinity(final BitSet affinity) { - sched_setaffinity(0, affinity); - } - + public static void sched_setaffinity(final BitSet affinity) { + sched_setaffinity(0, affinity); + } + public static void sched_setaffinity(final int pid, final BitSet affinity) { final CLibrary lib = CLibrary.INSTANCE; final cpu_set_t cpuset = new cpu_set_t(); @@ -158,7 +144,7 @@ public static int syscall(int number, Object... args) { } interface CLibrary extends Library { - CLibrary INSTANCE = (CLibrary) Native.loadLibrary(LIBRARY_NAME, CLibrary.class); + CLibrary INSTANCE = Native.load(LIBRARY_NAME, CLibrary.class); int sched_setaffinity(final int pid, final int cpusetsize, @@ -231,7 +217,7 @@ static int length(final byte[] data) { } @Override - protected List getFieldOrder() { + protected List getFieldOrder() { return FIELD_ORDER; } @@ -239,7 +225,7 @@ public String getSysname() { return new String(sysname, 0, length(sysname)); } - @SuppressWarnings({"UnusedDeclaration"}) + @SuppressWarnings("unused") public String getNodename() { return new String(nodename, 0, length(nodename)); } @@ -270,7 +256,7 @@ public String getMachine() { return new String(machine, 0, length(machine)); } - @SuppressWarnings({"UnusedDeclaration"}) + @SuppressWarnings("UnusedDeclaration") public String getDomainname() { return new String(domainname, 0, length(domainname)); } @@ -295,7 +281,7 @@ public cpu_set_t() { } } - @SuppressWarnings({"UnusedDeclaration"}) + @SuppressWarnings("UnusedDeclaration") public static void __CPU_ZERO(cpu_set_t cpuset) { for (NativeLong bits : cpuset.__bits) { bits.setValue(0L); @@ -310,25 +296,25 @@ public static long __CPUMASK(int cpu) { return 1L << (cpu % __NCPUBITS); } - @SuppressWarnings({"UnusedDeclaration"}) + @SuppressWarnings("UnusedDeclaration") public static void __CPU_SET(int cpu, cpu_set_t cpuset) { cpuset.__bits[__CPUELT(cpu)].setValue( cpuset.__bits[__CPUELT(cpu)].longValue() | __CPUMASK(cpu)); } - @SuppressWarnings({"UnusedDeclaration"}) + @SuppressWarnings("UnusedDeclaration") public static void __CPU_CLR(int cpu, cpu_set_t cpuset) { cpuset.__bits[__CPUELT(cpu)].setValue( cpuset.__bits[__CPUELT(cpu)].longValue() & ~__CPUMASK(cpu)); } - @SuppressWarnings({"UnusedDeclaration"}) + @SuppressWarnings("UnusedDeclaration") public static boolean __CPU_ISSET(int cpu, cpu_set_t cpuset) { return (cpuset.__bits[__CPUELT(cpu)].longValue() & __CPUMASK(cpu)) != 0; } @Override - protected List getFieldOrder() { + protected List getFieldOrder() { return FIELD_ORDER; } } diff --git a/affinity/src/main/java/net/openhft/affinity/impl/LinuxJNAAffinity.java b/affinity/src/main/java/net/openhft/affinity/impl/LinuxJNAAffinity.java index 1a4463319..d16fc3beb 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/LinuxJNAAffinity.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/LinuxJNAAffinity.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; import com.sun.jna.NativeLong; @@ -52,7 +38,7 @@ public enum LinuxJNAAffinity implements IAffinity { loaded = true; } catch (NoClassDefFoundError | UnsatisfiedLinkError e) { if (IS_LINUX) - LOGGER.warn("Unable to load jna library {}", e); + LOGGER.warn("Unable to load jna library", e); } LOADED = loaded; } @@ -72,7 +58,6 @@ public BitSet getAffinity() { return ret; } - // TODO: FIXME!!! CHANGE IAffinity TO SUPPORT PLATFORMS WITH 64+ CORES FIXME!!! @Override public void setAffinity(final BitSet affinity) { LinuxHelper.sched_setaffinity(affinity); diff --git a/affinity/src/main/java/net/openhft/affinity/impl/NoCpuLayout.java b/affinity/src/main/java/net/openhft/affinity/impl/NoCpuLayout.java index aabbc9d0e..e57b1decc 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/NoCpuLayout.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/NoCpuLayout.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; import net.openhft.affinity.CpuLayout; @@ -64,4 +50,9 @@ public int coreId(int cpuId) { public int threadId(int cpuId) { return 0; } + + @Override + public int pair(int cpuId) { + return 0; + } } diff --git a/affinity/src/main/java/net/openhft/affinity/impl/NullAffinity.java b/affinity/src/main/java/net/openhft/affinity/impl/NullAffinity.java index b33504b3c..4c5e8e304 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/NullAffinity.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/NullAffinity.java @@ -1,27 +1,12 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; import net.openhft.affinity.IAffinity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.management.ManagementFactory; import java.util.BitSet; /** @@ -38,7 +23,7 @@ public BitSet getAffinity() { @Override public void setAffinity(final BitSet affinity) { - LOGGER.trace("unable to set mask to {} as the JNIa nd JNA libraries and not loaded", Utilities.toHexString(affinity)); + LOGGER.trace("unable to set mask to {} as the JNI and JNA libraries not loaded", Utilities.toHexString(affinity)); } @Override @@ -48,8 +33,7 @@ public int getCpu() { @Override public int getProcessId() { - final String name = ManagementFactory.getRuntimeMXBean().getName(); - return Integer.parseInt(name.split("@")[0]); + return Utilities.currentProcessId(); } @Override diff --git a/affinity/src/main/java/net/openhft/affinity/impl/OSXJNAAffinity.java b/affinity/src/main/java/net/openhft/affinity/impl/OSXJNAAffinity.java index c9b7fa4b8..fd020fd21 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/OSXJNAAffinity.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/OSXJNAAffinity.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; import com.sun.jna.LastErrorException; @@ -24,7 +10,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.management.ManagementFactory; import java.util.BitSet; /** @@ -45,7 +30,7 @@ public BitSet getAffinity() { @Override public void setAffinity(final BitSet affinity) { - LOGGER.trace("unable to set mask to {} as the JNIa nd JNA libraries and not loaded", Utilities.toHexString(affinity)); + LOGGER.trace("unable to set mask to {} as the JNI and JNA libraries not loaded", Utilities.toHexString(affinity)); } @Override @@ -55,8 +40,7 @@ public int getCpu() { @Override public int getProcessId() { - final String name = ManagementFactory.getRuntimeMXBean().getName(); - return Integer.parseInt(name.split("@")[0]); + return Utilities.currentProcessId(); } @Override @@ -72,8 +56,7 @@ public int getThreadId() { } interface CLibrary extends Library { - CLibrary INSTANCE = (CLibrary) - Native.loadLibrary("libpthread.dylib", CLibrary.class); + CLibrary INSTANCE = Native.load("libpthread.dylib", CLibrary.class); int pthread_self() throws LastErrorException; } diff --git a/affinity/src/main/java/net/openhft/affinity/impl/PosixJNAAffinity.java b/affinity/src/main/java/net/openhft/affinity/impl/PosixJNAAffinity.java index f3efd1947..39aed93ab 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/PosixJNAAffinity.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/PosixJNAAffinity.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; import com.sun.jna.*; @@ -63,7 +49,7 @@ public enum PosixJNAAffinity implements IAffinity { INSTANCE.getAffinity(); loaded = true; } catch (UnsatisfiedLinkError e) { - LOGGER.warn("Unable to load jna library {}", e); + LOGGER.warn("Unable to load jna library", e); } LOADED = loaded; } @@ -191,8 +177,7 @@ public int getThreadId() { * @author BegemoT */ interface CLibrary extends Library { - CLibrary INSTANCE = (CLibrary) - Native.loadLibrary(LIBRARY_NAME, CLibrary.class); + CLibrary INSTANCE = Native.load(LIBRARY_NAME, CLibrary.class); int sched_setaffinity(final int pid, final int cpusetsize, diff --git a/affinity/src/main/java/net/openhft/affinity/impl/SolarisJNAAffinity.java b/affinity/src/main/java/net/openhft/affinity/impl/SolarisJNAAffinity.java index 2718187c5..fcb5b8db0 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/SolarisJNAAffinity.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/SolarisJNAAffinity.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; import com.sun.jna.LastErrorException; @@ -24,7 +10,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.management.ManagementFactory; import java.util.BitSet; /** @@ -45,7 +30,7 @@ public BitSet getAffinity() { @Override public void setAffinity(final BitSet affinity) { - LOGGER.trace("unable to set mask to {} as the JNIa nd JNA libraries and not loaded", Utilities.toHexString(affinity)); + LOGGER.trace("unable to set mask to {} as the JNI and JNA libraries not loaded", Utilities.toHexString(affinity)); } @Override @@ -55,8 +40,7 @@ public int getCpu() { @Override public int getProcessId() { - final String name = ManagementFactory.getRuntimeMXBean().getName(); - return Integer.parseInt(name.split("@")[0]); + return Utilities.currentProcessId(); } @Override @@ -72,8 +56,7 @@ public int getThreadId() { } interface CLibrary extends Library { - CLibrary INSTANCE = (CLibrary) - Native.loadLibrary("c", CLibrary.class); + CLibrary INSTANCE = Native.load("c", CLibrary.class); int pthread_self() throws LastErrorException; } diff --git a/affinity/src/main/java/net/openhft/affinity/impl/Utilities.java b/affinity/src/main/java/net/openhft/affinity/impl/Utilities.java index b81f9d919..babf6b360 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/Utilities.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/Utilities.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; import java.io.ByteArrayOutputStream; @@ -79,4 +65,30 @@ private static boolean is64Bit0() { systemProp = System.getProperty("java.vm.version"); return systemProp != null && systemProp.contains("_64"); } + + /** + * Returns the current process id. Uses {@code ProcessHandle} when running + * on Java 9 or later and falls back to parsing + * {@code RuntimeMXBean#getName()} on earlier versions. + * + * @return the process id or {@code -1} if it cannot be determined + */ + public static int currentProcessId() { + try { + // Java 9+ provides ProcessHandle which has a pid() method. + Class phClass = Class.forName("java.lang.ProcessHandle"); + Object current = phClass.getMethod("current").invoke(null); + long pid = (Long) phClass.getMethod("pid").invoke(current); + return (int) pid; + } catch (Throwable ignored) { + // ignore and fallback to the pre-Java 9 approach + } + + try { + String name = java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); + return Integer.parseInt(name.split("@")[0]); + } catch (Throwable e) { + return -1; + } + } } diff --git a/affinity/src/main/java/net/openhft/affinity/impl/VanillaCpuLayout.java b/affinity/src/main/java/net/openhft/affinity/impl/VanillaCpuLayout.java index d30c8bba3..4fac94fe4 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/VanillaCpuLayout.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/VanillaCpuLayout.java @@ -1,27 +1,15 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; import net.openhft.affinity.CpuLayout; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.LoggerFactory; import java.io.*; +import java.nio.charset.StandardCharsets; import java.util.*; import static java.lang.Integer.parseInt; @@ -60,7 +48,7 @@ public class VanillaCpuLayout implements CpuLayout { for (CpuInfo detail : cpuDetails) { error.append(detail).append('\n'); } - throw new AssertionError(error); + LoggerFactory.getLogger(VanillaCpuLayout.class).warn(error.toString()); } } @@ -113,7 +101,7 @@ private static InputStream openFile(String filename) throws FileNotFoundExceptio @NotNull public static VanillaCpuLayout fromCpuInfo(InputStream is) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8")); + BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); String line; List cpuDetails = new ArrayList<>(); CpuInfo details = new CpuInfo(); @@ -175,6 +163,19 @@ public int threadId(int cpuId) { return cpuDetails.get(cpuId).threadId; } + @Override + public int pair(int cpuId) { + for (int i = 0; i < cpuDetails.size(); i++) { + CpuInfo info = cpuDetails.get(i); + if (info.socketId == cpuDetails.get(cpuId).socketId && + info.coreId == cpuDetails.get(cpuId).coreId && + info.threadId != cpuDetails.get(cpuId).threadId) { + return i; + } + } + return 0; + } + @NotNull @Override public String toString() { diff --git a/affinity/src/main/java/net/openhft/affinity/impl/VersionHelper.java b/affinity/src/main/java/net/openhft/affinity/impl/VersionHelper.java index b54d75081..08a01ab19 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/VersionHelper.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/VersionHelper.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; public class VersionHelper { @@ -30,7 +16,7 @@ public VersionHelper(int major_, int minor_, int release_) { } public VersionHelper(String ver) { - if (ver != null && (ver = ver.trim()).length() > 0) { + if (ver != null && !(ver = ver.trim()).isEmpty()) { final String[] parts = ver.split("\\."); major = parts.length > 0 ? Integer.parseInt(parts[0]) : 0; minor = parts.length > 1 ? Integer.parseInt(parts[1]) : 0; @@ -46,7 +32,7 @@ public String toString() { } public boolean equals(Object o) { - if (o != null && (o instanceof VersionHelper)) { + if (o instanceof VersionHelper) { VersionHelper ver = (VersionHelper) o; return this.major == ver.major && this.minor == ver.minor @@ -61,7 +47,7 @@ public int hashCode() { return (major << 16) | (minor << 8) | release; } - @SuppressWarnings({"UnusedDeclaration"}) + @SuppressWarnings("unused") public boolean majorMinorEquals(final VersionHelper ver) { return ver != null && this.major == ver.major @@ -77,4 +63,3 @@ public boolean isSameOrNewer(final VersionHelper ver) { && this.release >= ver.release)); } } - diff --git a/affinity/src/main/java/net/openhft/affinity/impl/WindowsJNAAffinity.java b/affinity/src/main/java/net/openhft/affinity/impl/WindowsJNAAffinity.java index 1fdb8dc6a..bdeef6710 100644 --- a/affinity/src/main/java/net/openhft/affinity/impl/WindowsJNAAffinity.java +++ b/affinity/src/main/java/net/openhft/affinity/impl/WindowsJNAAffinity.java @@ -1,30 +1,15 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; -import com.sun.jna.LastErrorException; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.PointerType; +import com.sun.jna.*; import com.sun.jna.platform.win32.Kernel32; import com.sun.jna.platform.win32.WinDef; +import com.sun.jna.platform.win32.WinNT; import com.sun.jna.ptr.LongByReference; import net.openhft.affinity.IAffinity; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,6 +27,7 @@ public enum WindowsJNAAffinity implements IAffinity { INSTANCE; public static final boolean LOADED; private static final Logger LOGGER = LoggerFactory.getLogger(WindowsJNAAffinity.class); + private static final ThreadLocal currentAffinity = new ThreadLocal<>(); static { boolean loaded = false; @@ -58,26 +44,11 @@ public enum WindowsJNAAffinity implements IAffinity { @Override public BitSet getAffinity() { - final CLibrary lib = CLibrary.INSTANCE; - final LongByReference cpuset1 = new LongByReference(0); - final LongByReference cpuset2 = new LongByReference(0); - try { - - final int ret = lib.GetProcessAffinityMask(-1, cpuset1, cpuset2); - // Successful result is positive, according to the docs - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683213%28v=vs.85%29.aspx - if (ret <= 0) { - throw new IllegalStateException("GetProcessAffinityMask(( -1 ), &(" + cpuset1 + "), &(" + cpuset2 + ") ) return " + ret); - } - - long[] longs = new long[1]; - longs[0] = cpuset1.getValue(); - return BitSet.valueOf(longs); - } catch (Exception e) { - LOGGER.error(e.getMessage(), e); - } - - return new BitSet(); + BitSet bitSet = currentAffinity.get(); + if (bitSet != null) + return bitSet; + BitSet longs = getAffinity0(); + return longs != null ? longs : new BitSet(); } @Override @@ -99,10 +70,43 @@ public void setAffinity(final BitSet affinity) { int pid = getTid(); try { - lib.SetThreadAffinityMask(pid, aff); + lib.SetThreadAffinityMask(handle(pid), aff); } catch (LastErrorException e) { throw new IllegalStateException("SetThreadAffinityMask((" + pid + ") , &(" + affinity + ") ) errorNo=" + e.getErrorCode(), e); } + BitSet affinity2 = getAffinity0(); + assert affinity2 != null; + if (!affinity2.intersects(affinity)) { + LoggerFactory.getLogger(WindowsJNAAffinity.class).warn("Tried to set affinity to {} but was {} you may have insufficient access rights", affinity, affinity2); + } + currentAffinity.set((BitSet) affinity.clone()); + } + + @Nullable + private BitSet getAffinity0() { + final CLibrary lib = CLibrary.INSTANCE; + final LongByReference cpuset1 = new LongByReference(0); + final LongByReference cpuset2 = new LongByReference(0); + try { + + final int ret = lib.GetProcessAffinityMask(handle(-1), cpuset1, cpuset2); + // Successful result is positive, according to the docs + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683213%28v=vs.85%29.aspx + if (ret <= 0) { + throw new IllegalStateException("GetProcessAffinityMask(( -1 ), &(" + cpuset1 + "), &(" + cpuset2 + ") ) return " + ret); + } + + long[] longs = new long[1]; + longs[0] = cpuset1.getValue(); + return BitSet.valueOf(longs); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } + return null; + } + + private WinNT.HANDLE handle(int pid) { + return new WinNT.HANDLE(new Pointer(pid)); } public int getTid() { @@ -137,11 +141,11 @@ public int getThreadId() { * @author BegemoT */ private interface CLibrary extends Library { - CLibrary INSTANCE = (CLibrary) Native.loadLibrary("kernel32", CLibrary.class); + CLibrary INSTANCE = Native.load("kernel32", CLibrary.class); - int GetProcessAffinityMask(final int pid, final PointerType lpProcessAffinityMask, final PointerType lpSystemAffinityMask) throws LastErrorException; + int GetProcessAffinityMask(final WinNT.HANDLE pid, final PointerType lpProcessAffinityMask, final PointerType lpSystemAffinityMask) throws LastErrorException; - void SetThreadAffinityMask(final int pid, final WinDef.DWORD lpProcessAffinityMask) throws LastErrorException; + void SetThreadAffinityMask(final WinNT.HANDLE pid, final WinDef.DWORD lpProcessAffinityMask) throws LastErrorException; int GetCurrentThread() throws LastErrorException; } diff --git a/affinity/src/main/java/net/openhft/affinity/lockchecker/FileBasedLockChecker.java b/affinity/src/main/java/net/openhft/affinity/lockchecker/FileBasedLockChecker.java deleted file mode 100644 index 37224e104..000000000 --- a/affinity/src/main/java/net/openhft/affinity/lockchecker/FileBasedLockChecker.java +++ /dev/null @@ -1,83 +0,0 @@ -package net.openhft.affinity.lockchecker; - -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.text.SimpleDateFormat; -import java.util.Date; - -import static java.nio.charset.StandardCharsets.UTF_8; - -@SuppressWarnings("ResultOfMethodCallIgnored") -public class FileBasedLockChecker implements LockChecker { - - static final SimpleDateFormat df = new SimpleDateFormat("yyyy.MM" + ".dd 'at' HH:mm:ss z"); - private static final Logger LOGGER = LoggerFactory.getLogger(FileBasedLockChecker.class); - private static final LockChecker instance = new FileBasedLockChecker(); - - protected FileBasedLockChecker() { - //nothing - } - - public static LockChecker getInstance() { - return instance; - } - - private static File tmpDir() { - final File tempDir = new File(System.getProperty("java.io.tmpdir")); - - if (!tempDir.exists()) - tempDir.mkdirs(); - - return tempDir; - } - - @Override - public boolean isLockFree(int id) { - return !toFile(id).exists(); - } - - @Override - public boolean obtainLock(int id, String metaInfo) throws IOException { - File file = toFile(id); - file.delete(); - - try (Writer writer = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(file, false), UTF_8))) { - writer.write(metaInfo + "\n" + df.format(new Date())); - file.setWritable(true, false); - file.setExecutable(false, false); - return true; - } - } - - @Override - public boolean releaseLock(int id) { - return toFile(id).delete(); - } - - @Override - public String getMetaInfo(int id) throws IOException { - File file = toFile(id); - - if (!file.exists()) { - return null; - } - - try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), UTF_8))) { - final String firstLine = reader.readLine(); - if (firstLine == null) { - LOGGER.error(String.format("Empty lock file %s%n", file.getAbsolutePath())); - return null; - } - return firstLine.trim(); - } - } - - @NotNull - protected File toFile(int id) { - return new File(tmpDir(), "cpu-" + id + ".lock"); - } -} diff --git a/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java b/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java index c27a65bf9..eb2394ddd 100644 --- a/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java +++ b/affinity/src/main/java/net/openhft/affinity/lockchecker/FileLockBasedLockChecker.java @@ -1,164 +1,243 @@ -package net.openhft.affinity.lockchecker; - -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.channels.OverlappingFileLockException; -import java.nio.file.Files; -import java.nio.file.StandardOpenOption; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.attribute.PosixFilePermission; -import java.nio.file.attribute.PosixFilePermissions; -import java.util.Arrays; -import java.util.Date; -import java.util.HashSet; -import java.util.Set; - -import static java.nio.file.StandardOpenOption.*; -import static net.openhft.affinity.impl.VanillaCpuLayout.MAX_CPUS_SUPPORTED; - -public class FileLockBasedLockChecker extends FileBasedLockChecker { - - private static final Logger LOGGER = LoggerFactory.getLogger(FileLockBasedLockChecker.class); - private static final String OS = System.getProperty("os.name").toLowerCase(); - - private static final LockChecker instance = new FileLockBasedLockChecker(); - private static final HashSet openOptions = new HashSet<>(Arrays.asList(CREATE_NEW, WRITE, READ, SYNC)); - private static final FileAttribute> fileAttr = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-rw-rw-")); - private final LockReference[] locks = new LockReference[MAX_CPUS_SUPPORTED]; - - protected FileLockBasedLockChecker() { - //nothing - } - - public static LockChecker getInstance() { - return instance; - } - - @Override - public boolean isLockFree(int id) { - return isLockFree(toFile(id), id); - } - - private boolean isLockFree(File file, int id) { - //if no file exists - nobody has the lock for sure - if (!file.exists()) { - return true; - } - - //do we have the lock already? - LockReference existingLock = locks[id]; - if (existingLock != null) { - return false; - } - - //does another process have the lock? - try { - FileChannel fc = FileChannel.open(file.toPath(), WRITE); - FileLock fileLock = fc.tryLock(); - if (fileLock == null) { - return false; - } - } catch (IOException | OverlappingFileLockException e) { - LOGGER.error(String.format("Exception occurred whilst trying to check lock on file %s : %s%n", file.getAbsolutePath(), e)); - } - - //file is present but nobody has it locked - delete it - boolean deleted = file.delete(); - if (deleted) - LOGGER.info(String.format("Deleted %s as nobody has the lock", file.getAbsolutePath())); - else - LOGGER.warn(String.format("Nobody has the lock on %s. Delete failed", file.getAbsolutePath())); - - return true; - } - - @Override - public boolean obtainLock(int id, String metaInfo) throws IOException { - final File file = toFile(id); - if (!isLockFree(file, id)) { - return false; - } - - FileChannel fc = FileChannel.open(file.toPath(), openOptions, fileAttr); - FileLock fl = fc.tryLock(); - - if (fl == null) { - LOGGER.error(String.format("Could not obtain lock on file %s%n", file.getAbsolutePath())); - return false; - } else { - LOGGER.debug(String.format("Obtained lock on file %s (%s)%n", file.getAbsolutePath(), metaInfo)); - locks[id] = new LockReference(fc, fl); - - byte[] content = String.format("%s%n%s", metaInfo, df.format(new Date())).getBytes(); - ByteBuffer buffer = ByteBuffer.wrap(content); - while (buffer.hasRemaining()) { - fc.write(buffer); - } - return true; - } - } - - @Override - public boolean releaseLock(int id) { - LockReference lock = locks[id]; - if (lock == null) { - LOGGER.error(String.format("Cannot release lock for id %d as don't have it!", id)); - return false; - } - - try { - locks[id] = null; - lock.lock.release(); - lock.channel.close(); - toFile(id).delete(); - return true; - } catch (IOException e) { - LOGGER.error(String.format("Couldn't release lock for id %d due to exception: %s%n", id, e.getMessage())); - return false; - } - } - - @Override - public String getMetaInfo(int id) throws IOException { - final File file = toFile(id); - if (isLockFree(file, id)) { - LOGGER.warn("Cannot obtain lock on lock file {}", file.getAbsolutePath()); - return null; - } - - LockReference lr = locks[id]; - if (lr == null) { - return null; - } - FileChannel fc = lr.channel; - ByteBuffer buffer = ByteBuffer.allocate(64); - int len = fc.read(buffer, 0); - String content = len < 1 ? "" : new String(buffer.array(), 0, len); - if (content.isEmpty()) { - LOGGER.warn("Empty lock file {}", file.getAbsolutePath()); - return null; - } - return content.substring(0, content.indexOf("\n")); - } - - @NotNull - @Override - protected File toFile(int id) { - File file = super.toFile(id); - try { - if (file.exists() && OS.startsWith("linux")) { - Files.setPosixFilePermissions(file.toPath(), PosixFilePermissions.fromString("rwxrwxrwx")); - } - } catch (IOException e) { - LOGGER.warn("Unable to set file permissions \"rwxrwxrwx\" for {} due to {}", file.toString(), e); - } - return file; - } -} +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity.lockchecker; + +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; +import java.nio.file.NoSuchFileException; +import java.nio.file.OpenOption; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import static java.nio.file.StandardOpenOption.*; +import static net.openhft.affinity.impl.VanillaCpuLayout.MAX_CPUS_SUPPORTED; + +public class FileLockBasedLockChecker implements LockChecker { + + private static final int MAX_LOCK_RETRIES = 5; + private static final ThreadLocal dfTL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy.MM" + ".dd 'at' HH:mm:ss z")); + private static final FileAttribute> LOCK_FILE_ATTRIBUTES = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-rw-rw-")); + private static final Set LOCK_FILE_OPEN_OPTIONS = new HashSet<>(Arrays.asList(READ, WRITE, CREATE, SYNC)); + private static final Logger LOGGER = LoggerFactory.getLogger(FileLockBasedLockChecker.class); + private static final FileLockBasedLockChecker instance = new FileLockBasedLockChecker(); + private final LockReference[] locks = new LockReference[MAX_CPUS_SUPPORTED]; + + protected FileLockBasedLockChecker() { + //nothing + } + + public static LockChecker getInstance() { + return instance; + } + + @Override + public synchronized boolean isLockFree(int id) { + // check if this process already has the lock + if (locks[id] != null) { + return false; + } + + // check if another process has the lock + File lockFile = toFile(id); + try (final FileChannel channel = FileChannel.open(lockFile.toPath(), READ)) { + // if we can acquire a shared lock, nobody has an exclusive lock + try (final FileLock fileLock = channel.tryLock(0, Long.MAX_VALUE, true)) { + if (fileLock != null && fileLock.isValid()) { + if (!lockFile.delete()) { // try and clean up the orphaned lock file + LOGGER.debug("Couldn't delete orphaned lock file {}", lockFile); + } + return true; + } else { + // another process has an exclusive lock + return false; + } + } catch (OverlappingFileLockException e) { + // someone else (in the same JVM) has an exclusive lock + /* + * This shouldn't happen under normal circumstances, we have the singleton + * {@link #locks} array to prevent overlapping locks from the same JVM, but + * it can occur when there are multiple classloaders in the JVM + */ + return false; + } + } catch (NoSuchFileException e) { + // no lock file exists, nobody has the lock + return true; + } catch (IOException e) { + LOGGER.warn("An unexpected error occurred checking if the lock was free, assuming it's not", e); + return false; + } + } + + @Override + public synchronized boolean obtainLock(int id, int id2, String metaInfo) throws IOException { + int attempt = 0; + while (attempt < MAX_LOCK_RETRIES) { + try { + LockReference lockReference = tryAcquireLockOnFile(id, metaInfo); + if (lockReference != null) { + if (id2 <= 0) { + // no second lock to acquire, return success + locks[id] = lockReference; + return true; + } + LockReference lockReference2 = tryAcquireLockOnFile(id2, metaInfo); + if (lockReference2 != null) { + locks[id] = lockReference; + locks[id2] = lockReference2; + return true; + } else { + releaseLock(id); + } + } + return false; + } catch (ConcurrentLockFileDeletionException e) { + attempt++; + } + } + LOGGER.warn("Exceeded maximum retries for locking CPU {}, {}, failing acquire", id, id2); + return false; + } + + /** + * Attempts to acquire an exclusive lock on the core lock file. + *

    + * It will fail if another process already has an exclusive lock on the file. + * + * @param id The CPU ID to acquire + * @param metaInfo The meta-info to write to the file upon successful acquisition + * @return The {@link LockReference} if the lock was successfully acquired, null otherwise + * @throws IOException If an IOException occurs creating or writing to the file + * @throws ConcurrentLockFileDeletionException If another process deleted the file between us opening it and locking it + */ + private LockReference tryAcquireLockOnFile(int id, String metaInfo) throws IOException, ConcurrentLockFileDeletionException { + final File lockFile = toFile(id); + final FileChannel fileChannel = FileChannel.open(lockFile.toPath(), LOCK_FILE_OPEN_OPTIONS, LOCK_FILE_ATTRIBUTES); // NOSONAR + try { + final FileLock fileLock = fileChannel.tryLock(0, Long.MAX_VALUE, false); + if (fileLock == null) { + // someone else has a lock (exclusive or shared), fail to acquire + closeQuietly(fileChannel); + return null; + } else { + if (!lockFile.exists()) { + // someone deleted the file between us opening it and acquiring the lock, signal to retry + closeQuietly(fileLock, fileChannel); + throw new ConcurrentLockFileDeletionException(); + } else { + // we have the lock, the file exists. That's success. + writeMetaInfoToFile(fileChannel, metaInfo); + return new LockReference(fileChannel, fileLock); + } + } + } catch (OverlappingFileLockException e) { + // someone else (in the same JVM) has a lock, fail to acquire + /* + * This shouldn't happen under normal circumstances, we have the singleton + * {@link #locks} array to prevent overlapping locks from the same JVM, but + * it can occur when there are multiple classloaders in the JVM + */ + closeQuietly(fileChannel); + return null; + } + } + + private void writeMetaInfoToFile(FileChannel fc, String metaInfo) throws IOException { + byte[] content = String.format("%s%n%s", metaInfo, dfTL.get().format(new Date())).getBytes(); + ByteBuffer buffer = ByteBuffer.wrap(content); + while (buffer.hasRemaining()) { + //noinspection ResultOfMethodCallIgnored + fc.write(buffer); + } + } + + @Override + public synchronized boolean releaseLock(int id) { + if (locks[id] != null) { + final File lockFile = toFile(id); + if (!lockFile.delete()) { + LOGGER.warn("Couldn't delete lock file on release: {}", lockFile); + } + closeQuietly(locks[id].lock, locks[id].channel); + locks[id] = null; + return true; + } + return false; + } + + private void closeQuietly(AutoCloseable... closeables) { + for (AutoCloseable closeable : closeables) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (Exception e) { + LOGGER.warn("Error closing {}", closeable.getClass().getName(), e); + } + } + } + + @Override + public String getMetaInfo(int id) throws IOException { + final File file = toFile(id); + + LockReference lr = locks[id]; + if (lr != null) { + return readMetaInfoFromLockFileChannel(file, lr.channel); + } else { + try (FileChannel fc = FileChannel.open(file.toPath(), READ)) { + return readMetaInfoFromLockFileChannel(file, fc); + } catch (NoSuchFileException e) { + return null; + } + } + } + + private String readMetaInfoFromLockFileChannel(File lockFile, FileChannel lockFileChannel) throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(64); + int len = lockFileChannel.read(buffer, 0); + String content = len < 1 ? "" : new String(buffer.array(), 0, len); + if (content.isEmpty()) { + LOGGER.warn("Empty lock file {}", lockFile.getAbsolutePath()); + return null; + } + return content.substring(0, content.indexOf("\n")); + } + + @NotNull + protected File toFile(int id) { + assert id >= 0; + return new File(tmpDir(), "cpu-" + id + ".lock"); + } + + private File tmpDir() { + final File tempDir = new File(System.getProperty("java.io.tmpdir")); + + if (!tempDir.exists()) + tempDir.mkdirs(); + + return tempDir; + } + + /** + * Thrown when another process deleted the lock file between us opening the file and acquiring the lock + */ + static class ConcurrentLockFileDeletionException extends Exception { + private static final long serialVersionUID = 0L; + } +} diff --git a/affinity/src/main/java/net/openhft/affinity/lockchecker/LockChecker.java b/affinity/src/main/java/net/openhft/affinity/lockchecker/LockChecker.java index 0c95c335f..ddbe0d49c 100644 --- a/affinity/src/main/java/net/openhft/affinity/lockchecker/LockChecker.java +++ b/affinity/src/main/java/net/openhft/affinity/lockchecker/LockChecker.java @@ -1,18 +1,33 @@ -package net.openhft.affinity.lockchecker; - -import java.io.IOException; - -/** - * @author Tom Shercliff - */ - -public interface LockChecker { - - boolean isLockFree(int id); - - boolean obtainLock(int id, String metaInfo) throws IOException; - - boolean releaseLock(int id); - - String getMetaInfo(int id) throws IOException; -} +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity.lockchecker; + +import java.io.IOException; + +/** + * @author Tom Shercliff + */ + +public interface LockChecker { + + boolean isLockFree(int id); + + /** + * Obtain a lock for the given id. + */ + @Deprecated(/* to be removed in x.29 */) + default boolean obtainLock(int id, String metaInfo) throws IOException { + return obtainLock(id, 0, metaInfo); + } + + /** + * Obtain a lock for the given id and id2. The id2 is used to distinguish between + * multiple locks for the same core + */ + boolean obtainLock(int id, int id2, String metaInfo) throws IOException; + + boolean releaseLock(int id); + + String getMetaInfo(int id) throws IOException; +} diff --git a/affinity/src/main/java/net/openhft/affinity/lockchecker/LockReference.java b/affinity/src/main/java/net/openhft/affinity/lockchecker/LockReference.java index d5ed1c707..10e2b1bdf 100644 --- a/affinity/src/main/java/net/openhft/affinity/lockchecker/LockReference.java +++ b/affinity/src/main/java/net/openhft/affinity/lockchecker/LockReference.java @@ -1,22 +1,25 @@ -package net.openhft.affinity.lockchecker; - -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; - -/** - * @author Tom Shercliff - */ - -public class LockReference { - protected final FileChannel channel; - protected final FileLock lock; - - public LockReference(final FileChannel channel, final FileLock lock) { - this.channel = channel; - this.lock = lock; - } - - public FileChannel getChannel() { - return channel; - } -} +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity.lockchecker; + +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; + +/** + * @author Tom Shercliff + */ + +public class LockReference { + protected final FileChannel channel; + protected final FileLock lock; + + public LockReference(final FileChannel channel, final FileLock lock) { + this.channel = channel; + this.lock = lock; + } + + public FileChannel getChannel() { + return channel; + } +} diff --git a/affinity/src/main/java/net/openhft/affinity/main/AffinityTestMain.java b/affinity/src/main/java/net/openhft/affinity/main/AffinityTestMain.java index ef9070316..fcaa3281a 100644 --- a/affinity/src/main/java/net/openhft/affinity/main/AffinityTestMain.java +++ b/affinity/src/main/java/net/openhft/affinity/main/AffinityTestMain.java @@ -1,52 +1,53 @@ -package net.openhft.affinity.main; - -import net.openhft.affinity.Affinity; -import net.openhft.affinity.AffinityLock; - -import java.text.SimpleDateFormat; -import java.util.Date; - -/** - * @author Tom Shercliff - */ -public class AffinityTestMain { - private static final SimpleDateFormat df = new SimpleDateFormat("yyyy.MM" + ".dd 'at' HH:mm:ss z"); - - public static void main(String[] args) { - - int cpus = 1; - if (args.length == 0) { - cpus = AffinityLock.cpuLayout().cpus() / 12; - } else { - cpus = Integer.valueOf(args[0]); - } - - for (int i = 0; i < cpus; i++) { - acquireAndDoWork(); - } - } - - private static void acquireAndDoWork() { - - Thread t = new Thread(new Runnable() { - @Override - public void run() { - try (AffinityLock al = Affinity.acquireLock()) { - String threadName = Thread.currentThread().getName(); - System.out.println("Thread (" + threadName + ") locked onto cpu " + al.cpuId()); - - while (true) { - System.out.println(df.format(new Date()) + " - Thread (" + threadName + ") doing work on cpu " + al.cpuId() + ". IsAllocated = " + al.isAllocated() + ", isBound = " + al.isBound() + ". " + al.toString()); - - try { - Thread.sleep(10000L); - } catch (InterruptedException e) { - //nothing - } - } - } - } - }); - t.start(); - } -} +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity.main; + +import net.openhft.affinity.Affinity; +import net.openhft.affinity.AffinityLock; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * @author Tom Shercliff + */ +public class AffinityTestMain { + + public static void main(String[] args) { + + int cpus; + if (args.length == 0) { + cpus = AffinityLock.cpuLayout().cpus() / 12; + } else { + cpus = Integer.parseInt(args[0]); + } + + for (int i = 0; i < cpus; i++) { + acquireAndDoWork(); + } + } + + private static void acquireAndDoWork() { + + Thread t = new Thread(() -> { + final SimpleDateFormat df = new SimpleDateFormat("yyyy.MM" + ".dd 'at' HH:mm:ss z"); + try (AffinityLock al = Affinity.acquireLock()) { + String threadName = Thread.currentThread().getName(); + System.out.println("Thread (" + threadName + ") locked onto cpu " + al.cpuId()); + + while (true) { + System.out.println(df.format(new Date()) + " - Thread (" + threadName + ") doing work on cpu " + al.cpuId() + ". IsAllocated = " + al.isAllocated() + ", isBound = " + al.isBound() + ". " + al); + + try { + //noinspection BusyWait + Thread.sleep(10000L); + } catch (InterruptedException e) { + //nothing + } + } + } + }); + t.start(); + } +} diff --git a/affinity/src/main/java/net/openhft/ticker/ITicker.java b/affinity/src/main/java/net/openhft/ticker/ITicker.java index 70c3934df..3a9ab33d3 100644 --- a/affinity/src/main/java/net/openhft/ticker/ITicker.java +++ b/affinity/src/main/java/net/openhft/ticker/ITicker.java @@ -1,24 +1,25 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.ticker; -/* - * Created by Peter Lawrey on 13/07/15. +/** + * Abstraction of a high resolution time source used throughout the library. + *

    + * Implementations may be based on {@link System#nanoTime()} or platform + * specific timers such as the processor's time stamp counter accessed via + * JNI. The {@linkplain #ticks() tick values} returned are therefore + * implementation dependent. They always increase monotonically but the unit + * they represent can vary from nanoseconds to CPU cycles. + *

    + * Utility methods are provided to convert these raw ticks into conventional + * time units. For example {@link #toNanos(long)} converts the supplied number + * of ticks to nanoseconds and {@link #toMicros(double)} converts them to + * microseconds. + *

    + * This interface is typically accessed via the {@link net.openhft.ticker.Ticker} + * helper class which selects the best available implementation for the + * running platform. */ public interface ITicker { long nanoTime(); diff --git a/affinity/src/main/java/net/openhft/ticker/Ticker.java b/affinity/src/main/java/net/openhft/ticker/Ticker.java index d8ac5a1e6..4d057cf2a 100644 --- a/affinity/src/main/java/net/openhft/ticker/Ticker.java +++ b/affinity/src/main/java/net/openhft/ticker/Ticker.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.ticker; import net.openhft.ticker.impl.JNIClock; @@ -58,4 +44,4 @@ public static long toNanos(long ticks) { public static double toMicros(long ticks) { return INSTANCE.toMicros(ticks); } -} \ No newline at end of file +} diff --git a/affinity/src/main/java/net/openhft/ticker/impl/JNIClock.java b/affinity/src/main/java/net/openhft/ticker/impl/JNIClock.java index 0115c19cd..c50c3826c 100644 --- a/affinity/src/main/java/net/openhft/ticker/impl/JNIClock.java +++ b/affinity/src/main/java/net/openhft/ticker/impl/JNIClock.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.ticker.impl; import net.openhft.ticker.ITicker; @@ -65,13 +51,14 @@ static long tscToNano(final long tsc) { return (tsc * RDTSC_FACTOR) >> FACTOR_BITS; } + @SuppressWarnings("StatementWithEmptyBody") private static void estimateFrequency(int factor) { final long start = System.nanoTime(); long now; - while ((now = System.nanoTime()) == start) { + while (System.nanoTime() == start) { } - long end = start + factor * 1000000; + long end = start + factor * 1000000L; final long start0 = rdtsc0(); while ((now = System.nanoTime()) < end) { } @@ -103,4 +90,4 @@ public long toNanos(long ticks) { public double toMicros(double ticks) { return ticks * RDTSC_MICRO_FACTOR; } -} \ No newline at end of file +} diff --git a/affinity/src/main/java/net/openhft/ticker/impl/SystemClock.java b/affinity/src/main/java/net/openhft/ticker/impl/SystemClock.java index 8844ab460..cbbd3545e 100644 --- a/affinity/src/main/java/net/openhft/ticker/impl/SystemClock.java +++ b/affinity/src/main/java/net/openhft/ticker/impl/SystemClock.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.ticker.impl; import net.openhft.ticker.ITicker; @@ -47,4 +33,4 @@ public long toNanos(long ticks) { public double toMicros(double ticks) { return ticks / 1e3; } -} \ No newline at end of file +} diff --git a/affinity/src/main/java/software/chronicle/enterprise/internals/impl/NativeAffinity.java b/affinity/src/main/java/software/chronicle/enterprise/internals/impl/NativeAffinity.java index 5da995bcd..aa501a8f0 100644 --- a/affinity/src/main/java/software/chronicle/enterprise/internals/impl/NativeAffinity.java +++ b/affinity/src/main/java/software/chronicle/enterprise/internals/impl/NativeAffinity.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package software.chronicle.enterprise.internals.impl; import net.openhft.affinity.IAffinity; @@ -42,6 +28,7 @@ public enum NativeAffinity implements IAffinity { private native static long rdtsc0(); + @SuppressWarnings("restricted") private static boolean loadAffinityNativeLibrary() { try { System.loadLibrary("CEInternals"); diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityLockBindMain.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockBindMain.java index 68ec39073..b1ed7effe 100644 --- a/affinity/src/test/java/net/openhft/affinity/AffinityLockBindMain.java +++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockBindMain.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; import static net.openhft.affinity.AffinityStrategies.*; diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityLockDumpLocksTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockDumpLocksTest.java new file mode 100644 index 000000000..dd87e4f4e --- /dev/null +++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockDumpLocksTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity; + +import net.openhft.affinity.impl.VanillaCpuLayout; +import org.junit.Assume; +import org.junit.Test; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertTrue; + +public class AffinityLockDumpLocksTest extends BaseAffinityTest { + + private static void supressUnusedWarning(AutoCloseable c) { + // do nothing + } + + @Test + public void dumpLocksListsThreadsHoldingLocks() throws Exception { + Assume.assumeTrue(new File("/proc/cpuinfo").exists()); + + AffinityLock.cpuLayout(VanillaCpuLayout.fromCpuInfo()); + int nThreads = Math.min(3, Math.max(1, AffinityLock.PROCESSORS - 1)); + CountDownLatch acquired = new CountDownLatch(nThreads); + CountDownLatch release = new CountDownLatch(1); + List threads = new ArrayList<>(); + + for (int i = 0; i < nThreads; i++) { + String name = "worker-" + i; + Thread t = new Thread(() -> { + try (AffinityLock lock = AffinityLock.acquireLock()) { + supressUnusedWarning(lock); + acquired.countDown(); + release.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }, name); + threads.add(t); + t.start(); + } + + assertTrue("threads failed to acquire locks", acquired.await(5, TimeUnit.SECONDS)); + + String dump = AffinityLock.dumpLocks(); + for (Thread t : threads) { + assertTrue("Missing entry for " + t.getName(), dump.contains(t.getName())); + } + + release.countDown(); + for (Thread t : threads) { + t.join(); + } + } +} diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityLockMain.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockMain.java index 689eabda4..cf7388ec6 100644 --- a/affinity/src/test/java/net/openhft/affinity/AffinityLockMain.java +++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockMain.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; /** diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityLockReleaseTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockReleaseTest.java new file mode 100644 index 000000000..dc964296f --- /dev/null +++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockReleaseTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity; + +import net.openhft.affinity.impl.VanillaCpuLayout; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.assertEquals; + +/** + * Unit test to verify that releasing an {@link AffinityLock} restores the + * affinity mask back to {@link AffinityLock#BASE_AFFINITY}. + */ +public class AffinityLockReleaseTest extends BaseAffinityTest { + + @Test + public void acquireAndReleaseShouldRestoreBaseAffinity() throws Exception { + if (!new File("/proc/cpuinfo").exists()) { + System.out.println("Cannot run affinity test as this system doesn't have a /proc/cpuinfo file"); + return; + } + + // initialise CPU layout from the running machine so acquireLock works + AffinityLock.cpuLayout(VanillaCpuLayout.fromCpuInfo()); + + assertEquals(AffinityLock.BASE_AFFINITY, Affinity.getAffinity()); + AffinityLock lock = AffinityLock.acquireLock(); + assertEquals(1, Affinity.getAffinity().cardinality()); + lock.release(); + assertEquals(AffinityLock.BASE_AFFINITY, Affinity.getAffinity()); + } +} diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java index c4f8aa769..4e8ff5519 100644 --- a/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java +++ b/affinity/src/test/java/net/openhft/affinity/AffinityLockTest.java @@ -1,25 +1,12 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; import net.openhft.affinity.impl.Utilities; import net.openhft.affinity.impl.VanillaCpuLayout; -import net.openhft.affinity.testimpl.TestFileBasedLockChecker; +import net.openhft.chronicle.testframework.Waiters; +import org.hamcrest.MatcherAssert; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,8 +14,10 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.BitSet; import java.util.List; import static net.openhft.affinity.AffinityLock.PROCESSORS; @@ -39,23 +28,30 @@ /** * @author peter.lawrey */ -public class AffinityLockTest { +public class AffinityLockTest extends BaseAffinityTest { private static final Logger logger = LoggerFactory.getLogger(AffinityLockTest.class); - private final TestFileBasedLockChecker lockChecker = new TestFileBasedLockChecker(); + /** + * In Java 21 the toString contents of Thread changed to include an ID. This breaks the tests here in Java 21. + * Strip out the thread ID here so that existing tests continue to pass. + */ + private static String dumpLocks(AffinityLock[] locks) { + String value = LockInventory.dumpLocks(locks); + return value.replaceAll("#[0-9]+(,)?", ""); + } @Test public void dumpLocksI7() throws IOException { LockInventory lockInventory = new LockInventory(VanillaCpuLayout.fromCpuInfo("i7.cpuinfo")); AffinityLock[] locks = { - new AffinityLock(0, true, false, lockInventory), - new AffinityLock(1, false, false, lockInventory), - new AffinityLock(2, false, true, lockInventory), - new AffinityLock(3, false, true, lockInventory), - new AffinityLock(4, true, false, lockInventory), - new AffinityLock(5, false, false, lockInventory), - new AffinityLock(6, false, true, lockInventory), - new AffinityLock(7, false, true, lockInventory), + new AffinityLock(0, 0, true, false, lockInventory), + new AffinityLock(1, 5, false, false, lockInventory), + new AffinityLock(2, 6, false, true, lockInventory), + new AffinityLock(3, 7, false, true, lockInventory), + new AffinityLock(4, 0, true, false, lockInventory), + new AffinityLock(5, 1, false, false, lockInventory), + new AffinityLock(6, 2, false, true, lockInventory), + new AffinityLock(7, 3, false, true, lockInventory), }; locks[2].assignedThread = new Thread(new InterrupedThread(), "logger"); locks[2].assignedThread.start(); @@ -64,7 +60,7 @@ public void dumpLocksI7() throws IOException { locks[6].assignedThread = new Thread(new InterrupedThread(), "main"); locks[7].assignedThread = new Thread(new InterrupedThread(), "tcp"); locks[7].assignedThread.start(); - final String actual = LockInventory.dumpLocks(locks); + final String actual = dumpLocks(locks); assertEquals("0: General use CPU\n" + "1: CPU not available\n" + "2: Thread[logger,5,main] alive=true\n" + @@ -85,16 +81,16 @@ public void dumpLocksI7() throws IOException { public void dumpLocksI3() throws IOException { LockInventory lockInventory = new LockInventory(VanillaCpuLayout.fromCpuInfo("i3.cpuinfo")); AffinityLock[] locks = { - new AffinityLock(0, true, false, lockInventory), - new AffinityLock(1, false, true, lockInventory), - new AffinityLock(2, true, false, lockInventory), - new AffinityLock(3, false, true, lockInventory), + new AffinityLock(0, 0, true, false, lockInventory), + new AffinityLock(1, 3, false, true, lockInventory), + new AffinityLock(2, 0, true, false, lockInventory), + new AffinityLock(3, 1, false, true, lockInventory), }; locks[1].assignedThread = new Thread(new InterrupedThread(), "engine"); locks[1].assignedThread.start(); locks[3].assignedThread = new Thread(new InterrupedThread(), "main"); - final String actual = LockInventory.dumpLocks(locks); + final String actual = dumpLocks(locks); assertEquals("0: General use CPU\n" + "1: Thread[engine,5,main] alive=true\n" + "2: General use CPU\n" + @@ -108,13 +104,13 @@ public void dumpLocksI3() throws IOException { public void dumpLocksCoreDuo() throws IOException { LockInventory lockInventory = new LockInventory(VanillaCpuLayout.fromCpuInfo("core.duo.cpuinfo")); AffinityLock[] locks = { - new AffinityLock(0, true, false, lockInventory), - new AffinityLock(1, false, true, lockInventory), + new AffinityLock(0, 0, true, false, lockInventory), + new AffinityLock(1, 0, false, true, lockInventory), }; locks[1].assignedThread = new Thread(new InterrupedThread(), "engine"); locks[1].assignedThread.start(); - final String actual = LockInventory.dumpLocks(locks); + final String actual = dumpLocks(locks); assertEquals("0: General use CPU\n" + "1: Thread[engine,5,main] alive=true\n", actual); System.out.println(actual); @@ -151,6 +147,7 @@ public void assignReleaseThread() throws IOException { @Test public void resetAffinity() { + assumeTrue(System.getProperty("os.name").contains("nux")); assertTrue(Affinity.getAffinity().cardinality() > 1); try (AffinityLock lock = AffinityLock.acquireLock()) { assertEquals(1, Affinity.getAffinity().cardinality()); @@ -159,6 +156,7 @@ public void resetAffinity() { } assertEquals(1, Affinity.getAffinity().cardinality()); try (AffinityLock lock = AffinityLock.acquireLock()) { + assertNotNull(lock); } assertTrue(Affinity.getAffinity().cardinality() > 1); } @@ -213,20 +211,16 @@ public void testGettid() { @Test public void testAffinity() throws InterruptedException { - // System.out.println("Started"); logger.info("Started"); displayStatus(); try (AffinityLock al = AffinityLock.acquireLock()) { System.out.println("Main locked"); displayStatus(); - Thread t = new Thread(new Runnable() { - @Override - public void run() { - AffinityLock al2 = al.acquireLock(AffinityStrategies.SAME_SOCKET, AffinityStrategies.ANY); - System.out.println("Thread-0 locked"); - displayStatus(); - al2.release(); - } + Thread t = new Thread(() -> { + AffinityLock al2 = al.acquireLock(AffinityStrategies.SAME_SOCKET, AffinityStrategies.ANY); + System.out.println("Thread-0 locked"); + displayStatus(); + al2.release(); }); t.start(); t.join(); @@ -242,7 +236,7 @@ public void shouldReturnLockForSpecifiedCpu() { assumeTrue(Runtime.getRuntime().availableProcessors() > 3); try (final AffinityLock affinityLock = AffinityLock.acquireLock(3)) { - assertThat(affinityLock.cpuId(), is(3)); + MatcherAssert.assertThat(affinityLock.cpuId(), is(3)); } assertEquals(AffinityLock.BASE_AFFINITY, Affinity.getAffinity()); } @@ -254,11 +248,34 @@ public void lockFilesShouldBeRemovedOnRelease() { } final AffinityLock lock = AffinityLock.acquireLock(); - assertThat(Files.exists(Paths.get(lockChecker.doToFile(lock.cpuId()).getAbsolutePath())), is(true)); + Path lockFile = Paths.get(System.getProperty("java.io.tmpdir"), "cpu-" + lock.cpuId() + ".lock"); + assertTrue(Files.exists(lockFile)); lock.release(); - assertThat(Files.exists(Paths.get(lockChecker.doToFile(lock.cpuId()).getAbsolutePath())), is(false)); + assertFalse(Files.exists(lockFile)); + } + + @Test + public void wholeCoreLockReservesAllLogicalCpus() throws IOException { + if (!Utilities.ISLINUX || !new File("/proc/cpuinfo").exists()) { + return; + } + AffinityLock.cpuLayout(VanillaCpuLayout.fromCpuInfo()); + + CpuLayout layout = AffinityLock.cpuLayout(); + try (AffinityLock lock = AffinityLock.acquireCore()) { + int socketId = layout.socketId(lock.cpuId()); + int coreId = layout.coreId(lock.cpuId()); + for (int i = 0; i < layout.cpus(); i++) { + if (layout.socketId(i) == socketId && layout.coreId(i) == coreId) { + assertFalse("CPU " + i + " should be reserved", LockCheck.isCpuFree(i)); + } + } + } + for (int i = 0; i < layout.cpus(); i++) { + assertTrue("CPU " + i + " should not be reserved", LockCheck.isCpuFree(i)); + } } private void displayStatus() { @@ -271,15 +288,19 @@ public void testAffinityLockDescriptions() { return; } try (AffinityLock lock = AffinityLock.acquireLock("last")) { + assertNotNull(lock); assertEquals(PROCESSORS - 1, Affinity.getCpu()); } try (AffinityLock lock = AffinityLock.acquireLock("last")) { + assertNotNull(lock); assertEquals(PROCESSORS - 1, Affinity.getCpu()); } try (AffinityLock lock = AffinityLock.acquireLock("last-1")) { + assertNotNull(lock); assertEquals(PROCESSORS - 2, Affinity.getCpu()); } try (AffinityLock lock = AffinityLock.acquireLock("1")) { + assertNotNull(lock); assertEquals(1, Affinity.getCpu()); } try (AffinityLock lock = AffinityLock.acquireLock("any")) { @@ -295,4 +316,55 @@ public void testAffinityLockDescriptions() { assertFalse(lock.bound); } } + + @Test + public void acquireLockWithoutBindingDoesNotChangeAffinity() { + BitSet before = (BitSet) Affinity.getAffinity().clone(); + try (AffinityLock lock = AffinityLock.acquireLock(false)) { + assertFalse(lock.isBound()); + assertEquals(before, Affinity.getAffinity()); + } + assertEquals(before, Affinity.getAffinity()); + } + + @Test + public void testTooHighCpuId() { + assertFalse(AffinityLock.acquireLock(123456).isBound()); + } + + @Test + public void testNegativeCpuId() { + assertFalse(AffinityLock.acquireLock(-1).isBound()); + } + + @Test + public void testTooHighCpuId2() { + AffinityLock lock = AffinityLock.acquireLock(new int[]{123456}); + assertFalse(lock.isBound()); + } + + @Test(expected = IllegalStateException.class) + public void bindingTwoThreadsToSameCpuThrows() throws InterruptedException { + assumeTrue(Runtime.getRuntime().availableProcessors() > 1); + + final AffinityLock lock = AffinityLock.acquireLock(false); + Thread t = new Thread(() -> { + lock.bind(); + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + // ignored + } + }); + t.start(); + + Waiters.waitForCondition("Waiting for lock to be bound", lock::isBound, 1000); + + try { + lock.bind(); + } finally { + t.join(); + lock.release(); + } + } } diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityResetToBaseAffinityTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityResetToBaseAffinityTest.java new file mode 100644 index 000000000..86ad17fed --- /dev/null +++ b/affinity/src/test/java/net/openhft/affinity/AffinityResetToBaseAffinityTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity; + +import net.openhft.affinity.impl.VanillaCpuLayout; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.assertEquals; + +public class AffinityResetToBaseAffinityTest extends BaseAffinityTest { + + @Test + public void resettingShouldRestoreBaseAffinity() throws Exception { + if (!new File("/proc/cpuinfo").exists()) { + System.out.println("Cannot run affinity test as this system doesn't have a /proc/cpuinfo file"); + return; + } + + // initialise CPU layout from the running machine so acquireLock works + AffinityLock.cpuLayout(VanillaCpuLayout.fromCpuInfo()); + + assertEquals(AffinityLock.BASE_AFFINITY, Affinity.getAffinity()); + AffinityLock lock = AffinityLock.acquireLock(); + try { + assertEquals(1, Affinity.getAffinity().cardinality()); + + Affinity.resetToBaseAffinity(); + assertEquals(AffinityLock.BASE_AFFINITY, Affinity.getAffinity()); + } finally { + lock.release(); + } + } +} diff --git a/affinity/src/test/java/net/openhft/affinity/AffinitySupportMain.java b/affinity/src/test/java/net/openhft/affinity/AffinitySupportMain.java index 6841b3a65..d7cf5b35d 100644 --- a/affinity/src/test/java/net/openhft/affinity/AffinitySupportMain.java +++ b/affinity/src/test/java/net/openhft/affinity/AffinitySupportMain.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; /** diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityThreadFactoryMain.java b/affinity/src/test/java/net/openhft/affinity/AffinityThreadFactoryMain.java index 1b9fb99a2..3dbd15396 100644 --- a/affinity/src/test/java/net/openhft/affinity/AffinityThreadFactoryMain.java +++ b/affinity/src/test/java/net/openhft/affinity/AffinityThreadFactoryMain.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; import java.util.concurrent.Callable; @@ -37,16 +23,14 @@ private AffinityThreadFactoryMain() { public static void main(String... args) throws InterruptedException { for (int i = 0; i < 12; i++) - ES.submit(new Callable() { - @Override - public Void call() throws InterruptedException { - Thread.sleep(100); - return null; - } + ES.submit((Callable) () -> { + Thread.sleep(100); + return null; }); Thread.sleep(200); System.out.println("\nThe assignment of CPUs is\n" + AffinityLock.dumpLocks()); ES.shutdown(); + //noinspection ResultOfMethodCallIgnored ES.awaitTermination(1, TimeUnit.SECONDS); } } diff --git a/affinity/src/test/java/net/openhft/affinity/AffinityThreadFactoryTest.java b/affinity/src/test/java/net/openhft/affinity/AffinityThreadFactoryTest.java new file mode 100644 index 000000000..4c95b8bd4 --- /dev/null +++ b/affinity/src/test/java/net/openhft/affinity/AffinityThreadFactoryTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity; + +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; + +import java.util.Set; +import java.util.concurrent.*; + +import static org.junit.Assert.*; + +public class AffinityThreadFactoryTest extends BaseAffinityTest { + + @Before + public void checkLinux() { + Assume.assumeTrue(LockCheck.IS_LINUX); + } + + @Test + public void threadsReceiveDistinctCpus() throws InterruptedException { + int available = Math.max(1, AffinityLock.PROCESSORS - 1); + int nThreads = Math.min(4, available); + + ExecutorService es = Executors.newFixedThreadPool(nThreads, + new AffinityThreadFactory("test")); + + Set cpus = ConcurrentHashMap.newKeySet(); + CountDownLatch ready = new CountDownLatch(nThreads); + CountDownLatch finished = new CountDownLatch(nThreads); + + for (int i = 0; i < nThreads; i++) { + es.submit(() -> { + cpus.add(Affinity.getCpu()); + ready.countDown(); + try { + ready.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + finished.countDown(); + }); + } + + assertTrue(finished.await(5, TimeUnit.SECONDS)); + es.shutdown(); + es.awaitTermination(5, TimeUnit.SECONDS); + + assertFalse(cpus.contains(-1)); + assertEquals(nThreads, cpus.size()); + } +} diff --git a/affinity/src/test/java/net/openhft/affinity/BaseAffinityTest.java b/affinity/src/test/java/net/openhft/affinity/BaseAffinityTest.java new file mode 100644 index 000000000..f147ce33b --- /dev/null +++ b/affinity/src/test/java/net/openhft/affinity/BaseAffinityTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; + +import java.util.BitSet; + +import static org.junit.Assert.assertEquals; + +public class BaseAffinityTest { + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + private String originalTmpDir; + + @Before + public void setTmpDirectory() { + originalTmpDir = System.getProperty("java.io.tmpdir"); + System.setProperty("java.io.tmpdir", folder.getRoot().getAbsolutePath()); + } + + @After + public void restoreTmpDirectoryAndReleaseAllLocks() { + // don't leave any locks locked + for (int i = 0; i < AffinityLock.PROCESSORS; i++) { + LockCheck.releaseLock(i); + } + System.setProperty("java.io.tmpdir", originalTmpDir); + } + + @After + public void baseAffinity() { + BitSet affinity = Affinity.getAffinity(); + Affinity.resetToBaseAffinity(); + assertEquals(AffinityLock.BASE_AFFINITY, affinity); + } +} diff --git a/affinity/src/test/java/net/openhft/affinity/BootClassPathTest.java b/affinity/src/test/java/net/openhft/affinity/BootClassPathTest.java index 6c101e18e..17558a01e 100644 --- a/affinity/src/test/java/net/openhft/affinity/BootClassPathTest.java +++ b/affinity/src/test/java/net/openhft/affinity/BootClassPathTest.java @@ -1,15 +1,16 @@ +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ package net.openhft.affinity; import org.junit.Test; import static org.junit.Assert.assertTrue; -public class BootClassPathTest { +public class BootClassPathTest extends BaseAffinityTest { @Test public void shouldDetectClassesOnClassPath() { - if (!System.getProperty("java.version").startsWith("1.8")) - return; assertTrue(BootClassPath.INSTANCE.has("java.lang.Thread")); assertTrue(BootClassPath.INSTANCE.has("java.lang.Runtime")); } -} \ No newline at end of file +} diff --git a/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java b/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java index e59d0a805..f1a88ddc1 100644 --- a/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java +++ b/affinity/src/test/java/net/openhft/affinity/FileLockLockCheckTest.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; import net.openhft.affinity.testimpl.TestFileLockBasedLockChecker; @@ -32,32 +18,20 @@ /** * @author Tom Shercliff */ -public class FileLockLockCheckTest { +public class FileLockLockCheckTest extends BaseAffinityTest { - private static final String TMP = System.getProperty("java.io.tmpdir"); - private static final String TARGET = System.getProperty("project.build.directory", findTarget()); private final TestFileLockBasedLockChecker lockChecker = new TestFileLockBasedLockChecker(); - private int cpu = 11; - - private static String findTarget() { - for (File dir = new File(System.getProperty("user.dir")); dir != null; dir = dir.getParentFile()) { - File target = new File(dir, "target"); - if (target.exists()) - return target.getAbsolutePath(); - } - return TMP + "/target"; - } + private int cpu = 5; @Before public void before() { Assume.assumeTrue(IS_LINUX); - System.setProperty("java.io.tmpdir", TARGET + "/" + System.nanoTime()); } @Test public void test() throws IOException { Assert.assertTrue(LockCheck.isCpuFree(cpu)); - LockCheck.updateCpu(cpu); + LockCheck.updateCpu(cpu, 0); Assert.assertEquals(LockCheck.getPID(), LockCheck.getProcessForCpu(cpu)); } @@ -70,17 +44,50 @@ public void testPidOnLinux() { public void testReplace() throws IOException { cpu++; Assert.assertTrue(LockCheck.isCpuFree(cpu + 1)); - LockCheck.replacePid(cpu, 123L); + LockCheck.replacePid(cpu, 0, 123L); Assert.assertEquals(123L, LockCheck.getProcessForCpu(cpu)); } @Test public void shouldNotBlowUpIfPidFileIsEmpty() throws Exception { - LockCheck.updateCpu(cpu); + LockCheck.updateCpu(cpu, 0); final File file = lockChecker.doToFile(cpu); new RandomAccessFile(file, "rw").setLength(0); LockCheck.isCpuFree(cpu); } -} \ No newline at end of file + + @Test + public void lockFileDeletedWhileHeld() throws Exception { + cpu++; + + Assert.assertTrue(LockCheck.isCpuFree(cpu)); + LockCheck.updateCpu(cpu, 0); + + File lockFile = lockChecker.doToFile(cpu); + Assert.assertTrue(lockFile.exists()); + + Assert.assertTrue("Could not delete lock file", lockFile.delete()); + Assert.assertFalse(lockFile.exists()); + + Assert.assertFalse("CPU should remain locked despite missing file", LockCheck.isCpuFree(cpu)); + Assert.assertEquals(LockCheck.getPID(), LockCheck.getProcessForCpu(cpu)); + + LockCheck.releaseLock(cpu); + + Assert.assertTrue("Lock should be free after release", LockCheck.isCpuFree(cpu)); + LockCheck.updateCpu(cpu, 0); + + lockFile = lockChecker.doToFile(cpu); + Assert.assertTrue("Lock file should be recreated", lockFile.exists()); + } + + @Test + public void getProcessForCpuReturnsEmptyPidWhenNoFile() throws IOException { + int freeCpu = 99; + File lockFile = lockChecker.doToFile(freeCpu); + Assert.assertFalse(lockFile.exists()); + Assert.assertEquals(Integer.MIN_VALUE, LockCheck.getProcessForCpu(freeCpu)); + } +} diff --git a/affinity/src/test/java/net/openhft/affinity/InterrupedThread.java b/affinity/src/test/java/net/openhft/affinity/InterrupedThread.java index 8ea9c47e2..9e85c0790 100644 --- a/affinity/src/test/java/net/openhft/affinity/InterrupedThread.java +++ b/affinity/src/test/java/net/openhft/affinity/InterrupedThread.java @@ -1,20 +1,6 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; /** diff --git a/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java b/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java index 55cd8f7a6..3f19a4a2f 100644 --- a/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java +++ b/affinity/src/test/java/net/openhft/affinity/LockCheckTest.java @@ -1,23 +1,9 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity; -import net.openhft.affinity.testimpl.TestFileBasedLockChecker; +import net.openhft.affinity.testimpl.TestFileLockBasedLockChecker; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; @@ -33,32 +19,20 @@ /** * @author Rob Austin. */ -public class LockCheckTest { +public class LockCheckTest extends BaseAffinityTest { - private static final String TMP = System.getProperty("java.io.tmpdir"); - private static final String TARGET = System.getProperty("project.build.directory", findTarget()); - private final TestFileBasedLockChecker lockChecker = new TestFileBasedLockChecker(); + private final TestFileLockBasedLockChecker lockChecker = new TestFileLockBasedLockChecker(); private int cpu = 11; - private static String findTarget() { - for (File dir = new File(System.getProperty("user.dir")); dir != null; dir = dir.getParentFile()) { - File target = new File(dir, "target"); - if (target.exists()) - return target.getAbsolutePath(); - } - return TMP + "/target"; - } - @Before public void before() { Assume.assumeTrue(IS_LINUX); - System.setProperty("java.io.tmpdir", TARGET + "/" + System.nanoTime()); } @Test public void test() throws IOException { Assert.assertTrue(LockCheck.isCpuFree(cpu)); - LockCheck.updateCpu(cpu); + LockCheck.updateCpu(cpu, 0); Assert.assertEquals(LockCheck.getPID(), LockCheck.getProcessForCpu(cpu)); } @@ -67,17 +41,22 @@ public void testPidOnLinux() { Assert.assertTrue(LockCheck.isProcessRunning(LockCheck.getPID())); } + @Test + public void testNegativePidOnLinux() { + Assert.assertFalse(LockCheck.isProcessRunning(-1)); + } + @Test public void testReplace() throws IOException { cpu++; Assert.assertTrue(LockCheck.isCpuFree(cpu + 1)); - LockCheck.replacePid(cpu, 123L); + LockCheck.replacePid(cpu, 0, 123L); Assert.assertEquals(123L, LockCheck.getProcessForCpu(cpu)); } @Test public void shouldNotBlowUpIfPidFileIsEmpty() throws Exception { - LockCheck.updateCpu(cpu); + LockCheck.updateCpu(cpu, 0); final File file = lockChecker.doToFile(cpu); new RandomAccessFile(file, "rw").setLength(0); @@ -87,7 +66,7 @@ public void shouldNotBlowUpIfPidFileIsEmpty() throws Exception { @Test public void shouldNotBlowUpIfPidFileIsCorrupt() throws Exception { - LockCheck.updateCpu(cpu); + LockCheck.updateCpu(cpu, 0); final File file = lockChecker.doToFile(cpu); try (final FileWriter writer = new FileWriter(file, false)) { @@ -96,4 +75,4 @@ public void shouldNotBlowUpIfPidFileIsCorrupt() throws Exception { LockCheck.isCpuFree(cpu); } -} \ No newline at end of file +} diff --git a/affinity/src/test/java/net/openhft/affinity/MultiProcessAffinityTest.java b/affinity/src/test/java/net/openhft/affinity/MultiProcessAffinityTest.java new file mode 100644 index 000000000..63dd9105b --- /dev/null +++ b/affinity/src/test/java/net/openhft/affinity/MultiProcessAffinityTest.java @@ -0,0 +1,262 @@ +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity; + +import net.openhft.affinity.testimpl.TestFileLockBasedLockChecker; +import net.openhft.chronicle.testframework.process.JavaProcessBuilder; +import org.jetbrains.annotations.NotNull; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.StandardOpenOption; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static java.lang.String.format; +import static net.openhft.affinity.LockCheck.IS_LINUX; +import static org.junit.Assert.*; + +public class MultiProcessAffinityTest extends BaseAffinityTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(MultiProcessAffinityTest.class); + + @Before + public void setUp() { + Assume.assumeTrue(IS_LINUX); + } + + @Test + public void shouldNotAcquireLockOnCoresLockedByOtherProcesses() throws InterruptedException { + // run the separate affinity locker + final Process affinityLockerProcess = JavaProcessBuilder.create(AffinityLockerProcess.class) + .withJvmArguments("-Djava.io.tmpdir=" + folder.getRoot().getAbsolutePath()) + .withProgramArguments("last").start(); + try { + int lastCpuId = AffinityLock.PROCESSORS - 1; + + // wait for the CPU to be locked + long endTime = System.currentTimeMillis() + 5_000; + while (LockCheck.isCpuFree(lastCpuId)) { + //noinspection BusyWait + Thread.sleep(100); + if (System.currentTimeMillis() > endTime) { + LOGGER.info("Timed out waiting for the lock to be acquired: isAlive={}, exitCode={}", + affinityLockerProcess.isAlive(), affinityLockerProcess.isAlive() ? "N/A" : affinityLockerProcess.exitValue()); + JavaProcessBuilder.printProcessOutput("AffinityLockerProcess", affinityLockerProcess); + fail("Timed out waiting for the sub-process to acquire the lock"); + } + } + + try (AffinityLock lock = AffinityLock.acquireLock("last")) { + assertNotEquals(lastCpuId, lock.cpuId()); + } + } finally { + affinityLockerProcess.destroy(); + waitForProcessToEnd(5, "Affinity locker process", affinityLockerProcess, false); + } + } + + @Test + public void shouldAllocateCoresCorrectlyUnderContention() throws InterruptedException { + final int numberOfLockers = Math.max(2, Math.min(12, Runtime.getRuntime().availableProcessors())) / 2; + List lockers = new ArrayList<>(); + LOGGER.info("Running test with {} locker processes", numberOfLockers); + for (int i = 0; i < numberOfLockers; i++) { + lockers.add(JavaProcessBuilder.create(RepeatedAffinityLocker.class) + .withJvmArguments("-Djava.io.tmpdir=" + folder.getRoot().getAbsolutePath()) + .withProgramArguments("last", "30", "2").start()); + } + for (int i = 0; i < numberOfLockers; i++) { + final Process process = lockers.get(i); + waitForProcessToEnd(20, "Locking process", process); + } + } + + @Test + public void shouldAllocateCoresCorrectlyUnderContentionWithFailures() throws InterruptedException { + final int numberOfLockers = Math.max(2, Math.min(12, Runtime.getRuntime().availableProcessors())) / 2; + List lockers = new ArrayList<>(); + LOGGER.info("Running test with {} locker processes", numberOfLockers); + Process lockFileDropper = JavaProcessBuilder.create(LockFileDropper.class).start(); + for (int i = 0; i < numberOfLockers; i++) { + lockers.add(JavaProcessBuilder.create(RepeatedAffinityLocker.class) + .withJvmArguments("-Djava.io.tmpdir=" + folder.getRoot().getAbsolutePath()) + .withProgramArguments("last", "30", "2").start()); + } + for (int i = 0; i < numberOfLockers; i++) { + final Process process = lockers.get(i); + waitForProcessToEnd(20, "Locking process", process); + } + lockFileDropper.destroy(); + waitForProcessToEnd(5, "Lock file droppper", lockFileDropper); + } + + @Test + public void shouldBeAbleToAcquireLockLeftByOtherProcess() throws InterruptedException { + final Process process = JavaProcessBuilder.create(AffinityLockerThatDoesNotReleaseProcess.class) + .withJvmArguments("-Djava.io.tmpdir=" + folder.getRoot().getAbsolutePath()) + .withProgramArguments("last").start(); + waitForProcessToEnd(5, "Locking process", process); + // We should be able to acquire the lock despite the other process not explicitly releasing it + try (final AffinityLock acquired = AffinityLock.acquireLock("last")) { + assertEquals(AffinityLock.PROCESSORS - 1, acquired.cpuId()); + } + } + + private void waitForProcessToEnd(int timeToWaitSeconds, String processDescription, Process process) throws InterruptedException { + waitForProcessToEnd(timeToWaitSeconds, processDescription, process, true); + } + + private void waitForProcessToEnd(int timeToWaitSeconds, String processDescription, Process process, boolean checkExitCode) throws InterruptedException { + if (!process.waitFor(timeToWaitSeconds, TimeUnit.SECONDS)) { + JavaProcessBuilder.printProcessOutput(processDescription, process); + fail(processDescription + " didn't end in time"); + } + if (checkExitCode && process.exitValue() != 0) { + JavaProcessBuilder.printProcessOutput(processDescription, process); + fail(processDescription + " failed, see output above (exit value " + process.exitValue() + ")"); + } + } + + /** + * Repeatedly acquires and releases a lock on the specified core + */ + static class RepeatedAffinityLocker implements Callable { + + private static final Logger LOGGER = LoggerFactory.getLogger(RepeatedAffinityLocker.class); + private static final long PID = LockCheck.getPID(); + private final int iterations; + private final String cpuIdToLock; + + RepeatedAffinityLocker(String cpuIdToLock, int iterations) { + this.iterations = iterations; + this.cpuIdToLock = cpuIdToLock; + } + + public static void main(String[] args) throws InterruptedException, ExecutionException { + String cpuIdToLock = args[0]; + int iterations = Integer.parseInt(args[1]); + int threads = Integer.parseInt(args[2]); + + LOGGER.info("Acquiring lock with {} threads, {} iterations", threads, iterations); + ExecutorService executorService = Executors.newFixedThreadPool(threads); + final List> futures = executorService.invokeAll(IntStream.range(0, threads) + .mapToObj(tid -> new RepeatedAffinityLocker(cpuIdToLock, iterations)) + .collect(Collectors.toList())); + for (Future future : futures) { + future.get(); + } + executorService.shutdown(); + if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) { + throw new IllegalStateException("Executor service didn't shut down"); + } + } + + @Override + public Void call() throws Exception { + for (int i = 0; i < iterations; i++) { + LOGGER.info("******* Starting iteration {} at {}", i, LocalDateTime.now()); + try (final AffinityLock affinityLock = AffinityLock.acquireLock(cpuIdToLock)) { + if (affinityLock.isAllocated()) { + assertLockFileContainsMyPid(affinityLock); + Thread.sleep(ThreadLocalRandom.current().nextInt(50)); + assertLockFileContainsMyPid(affinityLock); + } else { + LOGGER.info("Couldn't get a lock"); + } + } + } + return null; + } + + private void assertLockFileContainsMyPid(AffinityLock affinityLock) throws IOException { + int lockPID = LockCheck.getProcessForCpu(affinityLock.cpuId()); + if (lockPID != PID) { + throw new IllegalStateException(format("PID in lock file is not mine (lockPID=%d, myPID=%d)", lockPID, PID)); + } + } + } + + /** + * Acquires a lock on the specified CPU, holds it until interrupted + */ + static class AffinityLockerProcess { + + private static final Logger LOGGER = LoggerFactory.getLogger(AffinityLockerProcess.class); + + public static void main(String[] args) { + String cpuIdToLock = args[0]; + + try (final AffinityLock affinityLock = AffinityLock.acquireLock(cpuIdToLock)) { + LOGGER.info("Got affinity lock {} at {}, CPU={}", affinityLock, LocalDateTime.now(), affinityLock.cpuId()); + Thread.sleep(Integer.MAX_VALUE); + LOGGER.error("Woke from sleep? this should never happen"); + } catch (InterruptedException e) { + // expected, just end + LOGGER.info("Interrupted at {} lock is released", LocalDateTime.now()); + } + } + } + + /** + * Acquires a lock then ends + */ + static class AffinityLockerThatDoesNotReleaseProcess { + private static final Logger LOGGER = LoggerFactory.getLogger(AffinityLockerThatDoesNotReleaseProcess.class); + + public static void main(String[] args) { + String cpuIdToLock = args[0]; + + final AffinityLock affinityLock = AffinityLock.acquireLock(cpuIdToLock); + LOGGER.info("Got affinity lock {} at {}, CPU={}", affinityLock, LocalDateTime.now(), affinityLock.cpuId()); + } + } + + /** + * Simulate failing processes by repeatedly dropping lock files + * with PIDs of non-existent processes + */ + static class LockFileDropper { + + public static void main(String[] args) throws InterruptedException, IOException { + while (Thread.currentThread().isInterrupted()) { + for (int cpu = 0; cpu < AffinityLock.PROCESSORS; cpu++) { + try { + File lockFile = toFile(cpu); + try (final FileChannel fc = FileChannel.open(lockFile.toPath(), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { + final long maxValue = Long.MAX_VALUE; // a PID that never exists + ByteBuffer buffer = ByteBuffer.wrap((maxValue + "\n").getBytes(StandardCharsets.UTF_8)); + while (buffer.hasRemaining()) { + //noinspection ResultOfMethodCallIgnored + fc.write(buffer); + } + } + } catch (FileAlreadyExistsException e) { + LOGGER.info("Failed, trying again"); + } + //noinspection BusyWait + Thread.sleep(ThreadLocalRandom.current().nextInt(50)); + } + } + } + + @NotNull + static File toFile(int id) { + return new TestFileLockBasedLockChecker().doToFile(id); + } + } +} diff --git a/affinity/src/test/java/net/openhft/affinity/impl/AbstractAffinityImplTest.java b/affinity/src/test/java/net/openhft/affinity/impl/AbstractAffinityImplTest.java index f858e8086..6c1cc5fbf 100644 --- a/affinity/src/test/java/net/openhft/affinity/impl/AbstractAffinityImplTest.java +++ b/affinity/src/test/java/net/openhft/affinity/impl/AbstractAffinityImplTest.java @@ -1,45 +1,31 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; +import net.openhft.affinity.BaseAffinityTest; import net.openhft.affinity.IAffinity; import org.junit.After; import org.junit.Test; import java.util.BitSet; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * @author cheremin * @since 29.12.11, 20:25 */ -public abstract class AbstractAffinityImplTest { +public abstract class AbstractAffinityImplTest extends BaseAffinityTest { - protected static final int CORES = Runtime.getRuntime().availableProcessors(); - protected static final BitSet CORES_MASK = new BitSet(CORES); + private static final int CORES = Runtime.getRuntime().availableProcessors(); + private static final BitSet CORES_MASK = new BitSet(CORES); static { CORES_MASK.set(0, CORES, true); } - public abstract IAffinity getImpl(); + protected abstract IAffinity getImpl(); @Test public void getAffinityCompletesGracefully() { @@ -49,10 +35,7 @@ public void getAffinityCompletesGracefully() { @Test public void getAffinityReturnsValidValue() { final BitSet affinity = getImpl().getAffinity(); - assertTrue( - "Affinity mask " + Utilities.toBinaryString(affinity) + " must be non-empty", - !affinity.isEmpty() - ); + assertFalse("Affinity mask " + Utilities.toBinaryString(affinity) + " must be non-empty", affinity.isEmpty()); final long allCoresMask = (1L << CORES) - 1; assertTrue( "Affinity mask " + Utilities.toBinaryString(affinity) + " must be <=(2^" + CORES + "-1 = " + allCoresMask + ")", diff --git a/affinity/src/test/java/net/openhft/affinity/impl/CpuInfoLayoutMappingTest.java b/affinity/src/test/java/net/openhft/affinity/impl/CpuInfoLayoutMappingTest.java new file mode 100644 index 000000000..7687f6967 --- /dev/null +++ b/affinity/src/test/java/net/openhft/affinity/impl/CpuInfoLayoutMappingTest.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity.impl; + +import net.openhft.affinity.BaseAffinityTest; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.assertEquals; + +public class CpuInfoLayoutMappingTest extends BaseAffinityTest { + + @Test + public void verifyI7CpuInfoMapping() throws IOException { + final InputStream i7 = getClass().getClassLoader().getResourceAsStream("i7.cpuinfo"); + VanillaCpuLayout vcl = VanillaCpuLayout.fromCpuInfo(i7); + assertEquals("" + + "0: CpuInfo{socketId=0, coreId=0, threadId=0}\n" + + "1: CpuInfo{socketId=0, coreId=1, threadId=0}\n" + + "2: CpuInfo{socketId=0, coreId=2, threadId=0}\n" + + "3: CpuInfo{socketId=0, coreId=3, threadId=0}\n" + + "4: CpuInfo{socketId=0, coreId=0, threadId=1}\n" + + "5: CpuInfo{socketId=0, coreId=1, threadId=1}\n" + + "6: CpuInfo{socketId=0, coreId=2, threadId=1}\n" + + "7: CpuInfo{socketId=0, coreId=3, threadId=1}\n", + vcl.toString()); + } +} + diff --git a/affinity/src/test/java/net/openhft/affinity/impl/LinuxJNAAffinityTest.java b/affinity/src/test/java/net/openhft/affinity/impl/LinuxJNAAffinityTest.java index 52ab34bfe..cf0db12bd 100644 --- a/affinity/src/test/java/net/openhft/affinity/impl/LinuxJNAAffinityTest.java +++ b/affinity/src/test/java/net/openhft/affinity/impl/LinuxJNAAffinityTest.java @@ -1,22 +1,9 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; +import net.openhft.affinity.BaseAffinityTest; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; @@ -28,7 +15,7 @@ /* * Created by Peter Lawrey on 23/03/16. */ -public class LinuxJNAAffinityTest { +public class LinuxJNAAffinityTest extends BaseAffinityTest { @BeforeClass public static void checkJniLibraryPresent() { Assume.assumeTrue(LinuxJNAAffinity.LOADED); diff --git a/affinity/src/test/java/net/openhft/affinity/impl/NativeAffinityImpTest.java b/affinity/src/test/java/net/openhft/affinity/impl/NativeAffinityImpTest.java index 4f038e3d1..2b9cbb73a 100644 --- a/affinity/src/test/java/net/openhft/affinity/impl/NativeAffinityImpTest.java +++ b/affinity/src/test/java/net/openhft/affinity/impl/NativeAffinityImpTest.java @@ -1,23 +1,9 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; -import net.openhft.affinity.AffinitySupport; +import net.openhft.affinity.Affinity; import net.openhft.affinity.IAffinity; import org.junit.Assume; import org.junit.BeforeClass; @@ -45,7 +31,7 @@ public IAffinity getImpl() { public void testGettid() { System.out.println("pid=" + getImpl().getProcessId()); System.out.println("tid=" + getImpl().getThreadId()); - AffinitySupport.setThreadId(); + Affinity.setThreadId(); for (int j = 0; j < 3; j++) { final int runs = 100000; @@ -53,7 +39,9 @@ public void testGettid() { long time = 0; for (int i = 0; i < runs; i++) { long start = System.nanoTime(); - tid = Thread.currentThread().getId(); + @SuppressWarnings("deprecation") + long tid0 = Thread.currentThread().getId(); + tid = tid0; time += System.nanoTime() - start; assertTrue(tid > 0); assertTrue(tid < 1 << 16); diff --git a/affinity/src/test/java/net/openhft/affinity/impl/PosixJNAAffinityTest.java b/affinity/src/test/java/net/openhft/affinity/impl/PosixJNAAffinityTest.java index 034aa0a69..180506e70 100644 --- a/affinity/src/test/java/net/openhft/affinity/impl/PosixJNAAffinityTest.java +++ b/affinity/src/test/java/net/openhft/affinity/impl/PosixJNAAffinityTest.java @@ -1,30 +1,15 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; import net.openhft.affinity.Affinity; -import net.openhft.affinity.AffinitySupport; import net.openhft.affinity.IAffinity; -import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; /** * @author peter.lawrey @@ -32,7 +17,8 @@ public class PosixJNAAffinityTest extends AbstractAffinityImplTest { @BeforeClass public static void checkJniLibraryPresent() { - Assume.assumeTrue("linux".equalsIgnoreCase(System.getProperty("os.name"))); + assumeTrue("TODO FIX JNA library is not used, but the test is flaky", false); + assumeTrue("linux".equalsIgnoreCase(System.getProperty("os.name"))); } @Override @@ -44,7 +30,7 @@ public IAffinity getImpl() { public void testGettid() { System.out.println("pid=" + getImpl().getProcessId()); System.out.println("tid=" + getImpl().getThreadId()); - AffinitySupport.setThreadId(); + Affinity.setThreadId(); for (int j = 0; j < 3; j++) { final int runs = 100000; @@ -52,7 +38,9 @@ public void testGettid() { long time = 0; for (int i = 0; i < runs; i++) { long start = System.nanoTime(); - tid = Thread.currentThread().getId(); + @SuppressWarnings("deprecation") + long tid0 = Thread.currentThread().getId(); + tid = tid0; time += System.nanoTime() - start; assertTrue(tid > 0); assertTrue(tid < 1 << 24); diff --git a/affinity/src/test/java/net/openhft/affinity/impl/UtilitiesTest.java b/affinity/src/test/java/net/openhft/affinity/impl/UtilitiesTest.java new file mode 100644 index 000000000..80eed46ea --- /dev/null +++ b/affinity/src/test/java/net/openhft/affinity/impl/UtilitiesTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity.impl; + +import net.openhft.affinity.BaseAffinityTest; +import org.junit.Test; + +import java.util.BitSet; + +import static org.junit.Assert.assertEquals; + +public class UtilitiesTest extends BaseAffinityTest { + + private static String hex(BitSet set, int... bits) { + set.clear(); + for (int b : bits) { + set.set(b); + } + return Utilities.toHexString(set); + } + + private static String bin(BitSet set, int... bits) { + set.clear(); + for (int b : bits) { + set.set(b); + } + return Utilities.toBinaryString(set); + } + + @Test + public void testToHexString() { + BitSet set = new BitSet(); + assertEquals("", hex(set)); + assertEquals("1", hex(set, 0)); + assertEquals("10", hex(set, 4)); + assertEquals(Long.toHexString(1L << 63), hex(set, 63)); + assertEquals("01", hex(set, 64)); + assertEquals("101", hex(set, 0, 128)); + } + + @Test + public void testToBinaryString() { + BitSet set = new BitSet(); + assertEquals("", bin(set)); + assertEquals("1", bin(set, 0)); + assertEquals("10000", bin(set, 4)); + assertEquals(Long.toBinaryString(1L << 63), bin(set, 63)); + assertEquals("01", bin(set, 64)); + assertEquals("101", bin(set, 0, 128)); + } +} diff --git a/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPairTest.java b/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPairTest.java new file mode 100644 index 000000000..c356f3c81 --- /dev/null +++ b/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPairTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity.impl; + +import net.openhft.affinity.BaseAffinityTest; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for {@link VanillaCpuLayout#pair(int)} using sample cpuinfo files. + */ +public class VanillaCpuLayoutPairTest extends BaseAffinityTest { + + @Test + public void testPairForI7() throws IOException { + try (InputStream is = getClass().getClassLoader().getResourceAsStream("i7.cpuinfo")) { + VanillaCpuLayout layout = VanillaCpuLayout.fromCpuInfo(is); + assertEquals(4, layout.pair(0)); + assertEquals(5, layout.pair(1)); + assertEquals(6, layout.pair(2)); + assertEquals(7, layout.pair(3)); + assertEquals(0, layout.pair(4)); + assertEquals(1, layout.pair(5)); + assertEquals(2, layout.pair(6)); + assertEquals(3, layout.pair(7)); + } + } + + @Test + public void testPairForI3() throws IOException { + try (InputStream is = getClass().getClassLoader().getResourceAsStream("i3.cpuinfo")) { + VanillaCpuLayout layout = VanillaCpuLayout.fromCpuInfo(is); + assertEquals(2, layout.pair(0)); + assertEquals(3, layout.pair(1)); + assertEquals(0, layout.pair(2)); + assertEquals(1, layout.pair(3)); + } + } +} diff --git a/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPropertiesParseTest.java b/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPropertiesParseTest.java new file mode 100644 index 000000000..7452c060f --- /dev/null +++ b/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutPropertiesParseTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity.impl; + +import net.openhft.affinity.BaseAffinityTest; +import org.junit.Test; + +import java.io.InputStream; + +import static org.junit.Assert.assertEquals; + +public class VanillaCpuLayoutPropertiesParseTest extends BaseAffinityTest { + + @Test + public void testCountsI7() throws Exception { + InputStream is = getClass().getClassLoader().getResourceAsStream("i7.properties"); + VanillaCpuLayout vcl = VanillaCpuLayout.fromProperties(is); + assertEquals(8, vcl.cpus()); + assertEquals(1, vcl.sockets()); + assertEquals(4, vcl.coresPerSocket()); + assertEquals(2, vcl.threadsPerCore()); + } + + @Test + public void testCountsDualXeon() throws Exception { + InputStream is = getClass().getClassLoader().getResourceAsStream("dual.xeon.properties"); + VanillaCpuLayout vcl = VanillaCpuLayout.fromProperties(is); + assertEquals(4, vcl.cpus()); + assertEquals(2, vcl.sockets()); + assertEquals(1, vcl.coresPerSocket()); + assertEquals(2, vcl.threadsPerCore()); + } + + @Test + public void testCountsDualE5405() throws Exception { + InputStream is = getClass().getClassLoader().getResourceAsStream("dual.E5405.properties"); + VanillaCpuLayout vcl = VanillaCpuLayout.fromProperties(is); + assertEquals(8, vcl.cpus()); + assertEquals(2, vcl.sockets()); + assertEquals(4, vcl.coresPerSocket()); + assertEquals(1, vcl.threadsPerCore()); + } + + @Test + public void testCountsI3() throws Exception { + InputStream is = getClass().getClassLoader().getResourceAsStream("i3.properties"); + VanillaCpuLayout vcl = VanillaCpuLayout.fromProperties(is); + assertEquals(4, vcl.cpus()); + assertEquals(1, vcl.sockets()); + assertEquals(2, vcl.coresPerSocket()); + assertEquals(2, vcl.threadsPerCore()); + } +} diff --git a/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutTest.java b/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutTest.java index e58206818..0e90d5069 100644 --- a/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutTest.java +++ b/affinity/src/test/java/net/openhft/affinity/impl/VanillaCpuLayoutTest.java @@ -1,33 +1,20 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; +import net.openhft.affinity.BaseAffinityTest; import org.junit.Test; import java.io.IOException; import java.io.InputStream; -import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertEquals; /** * @author peter.lawrey */ -public class VanillaCpuLayoutTest { +public class VanillaCpuLayoutTest extends BaseAffinityTest { @Test public void testFromCpuInfoI7() throws IOException { diff --git a/affinity/src/test/java/net/openhft/affinity/impl/VersionHelperTest.java b/affinity/src/test/java/net/openhft/affinity/impl/VersionHelperTest.java index 232d12c3c..a7f21afff 100644 --- a/affinity/src/test/java/net/openhft/affinity/impl/VersionHelperTest.java +++ b/affinity/src/test/java/net/openhft/affinity/impl/VersionHelperTest.java @@ -1,26 +1,13 @@ /* - * Copyright 20127chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.affinity.impl; +import net.openhft.affinity.BaseAffinityTest; import org.junit.Assert; import org.junit.Test; -public class VersionHelperTest { +public class VersionHelperTest extends BaseAffinityTest { @Test public void isSameOrNewerTest() { diff --git a/affinity/src/test/java/net/openhft/affinity/testimpl/TestFileBasedLockChecker.java b/affinity/src/test/java/net/openhft/affinity/testimpl/TestFileBasedLockChecker.java deleted file mode 100644 index dd9b61881..000000000 --- a/affinity/src/test/java/net/openhft/affinity/testimpl/TestFileBasedLockChecker.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.openhft.affinity.testimpl; - -import net.openhft.affinity.lockchecker.FileBasedLockChecker; - -import java.io.File; - -public class TestFileBasedLockChecker extends FileBasedLockChecker { - - public File doToFile(int cpu) { - return toFile(cpu); - } -} diff --git a/affinity/src/test/java/net/openhft/affinity/testimpl/TestFileLockBasedLockChecker.java b/affinity/src/test/java/net/openhft/affinity/testimpl/TestFileLockBasedLockChecker.java index 75d15f449..56b47d9e1 100644 --- a/affinity/src/test/java/net/openhft/affinity/testimpl/TestFileLockBasedLockChecker.java +++ b/affinity/src/test/java/net/openhft/affinity/testimpl/TestFileLockBasedLockChecker.java @@ -1,12 +1,15 @@ -package net.openhft.affinity.testimpl; - -import net.openhft.affinity.lockchecker.FileLockBasedLockChecker; - -import java.io.File; - -public class TestFileLockBasedLockChecker extends FileLockBasedLockChecker { - - public File doToFile(int cpu) { - return toFile(cpu); - } -} +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.affinity.testimpl; + +import net.openhft.affinity.lockchecker.FileLockBasedLockChecker; + +import java.io.File; + +public class TestFileLockBasedLockChecker extends FileLockBasedLockChecker { + + public File doToFile(int cpu) { + return toFile(cpu); + } +} diff --git a/affinity/src/test/java/net/openhft/ticker/impl/JNIClockTest.java b/affinity/src/test/java/net/openhft/ticker/impl/JNIClockTest.java index 8af7760de..359c06e02 100644 --- a/affinity/src/test/java/net/openhft/ticker/impl/JNIClockTest.java +++ b/affinity/src/test/java/net/openhft/ticker/impl/JNIClockTest.java @@ -1,23 +1,10 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package net.openhft.ticker.impl; import net.openhft.affinity.Affinity; +import net.openhft.affinity.BaseAffinityTest; import org.junit.Ignore; import org.junit.Test; @@ -26,7 +13,7 @@ /* * Created by Peter Lawrey on 13/07/15. */ -public class JNIClockTest { +public class JNIClockTest extends BaseAffinityTest { @Test @Ignore("TODO Fix") @@ -78,4 +65,4 @@ public void testJitter() { System.out.println(((long) (clock.toMicros(time[i]) * 10)) / 10.0 + ", " + ((long) (clock.toMicros(length[i]) * 10) / 10.0)); } } -} \ No newline at end of file +} diff --git a/affinity/src/test/java/org/junit/Assert.java b/affinity/src/test/java/org/junit/Assert.java deleted file mode 100644 index 994c64395..000000000 --- a/affinity/src/test/java/org/junit/Assert.java +++ /dev/null @@ -1,886 +0,0 @@ -/* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.junit; - -import org.hamcrest.Matcher; -import org.hamcrest.MatcherAssert; -import org.junit.internal.ArrayComparisonFailure; -import org.junit.internal.ExactComparisonCriteria; -import org.junit.internal.InexactComparisonCriteria; - -/** - * TODO replace with 4.12 when it is released with a simple bug fix. - *

    - * A set of assertion methods useful for writing tests. Only failed assertions - * are recorded. These methods can be used directly: - * Assert.assertEquals(...), however, they read better if they - * are referenced through static import:
    - *

    - *

    - * import static org.junit.Assert.*;
    - *    ...
    - *    assertEquals(...);
    - * 
    - * - * @see AssertionError - * @since 4.0 - */ -public class Assert { - /** - * Protect constructor since it is a static only class - */ - protected Assert() { - } - - /** - * Asserts that a condition is true. If it isn't it throws an - * {@link AssertionError} with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param condition condition to be checked - */ - static public void assertTrue(String message, boolean condition) { - if (!condition) { - fail(message); - } - } - - /** - * Asserts that a condition is true. If it isn't it throws an - * {@link AssertionError} without a message. - * - * @param condition condition to be checked - */ - static public void assertTrue(boolean condition) { - assertTrue(null, condition); - } - - /** - * Asserts that a condition is false. If it isn't it throws an - * {@link AssertionError} with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param condition condition to be checked - */ - static public void assertFalse(String message, boolean condition) { - assertTrue(message, !condition); - } - - /** - * Asserts that a condition is false. If it isn't it throws an - * {@link AssertionError} without a message. - * - * @param condition condition to be checked - */ - static public void assertFalse(boolean condition) { - assertFalse(null, condition); - } - - /** - * Fails a test with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @see AssertionError - */ - static public void fail(String message) { - if (message == null) { - throw new AssertionError(); - } - throw new AssertionError(message); - } - - /** - * Fails a test with no message. - */ - static public void fail() { - fail(null); - } - - /** - * Asserts that two objects are equal. If they are not, an - * {@link AssertionError} is thrown with the given message. If - * expected and actual are null, - * they are considered equal. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expected expected value - * @param actual actual value - */ - static public void assertEquals(String message, Object expected, - Object actual) { - if (equalsRegardingNull(expected, actual)) { - return; - - } else if (expected instanceof String && actual instanceof String) { - String cleanMessage = message == null ? "" : message; - throw new ComparisonFailure(cleanMessage, (String) expected, - (String) actual); - - } else { - failNotEquals(message, expected, actual); - } - } - - private static boolean equalsRegardingNull(Object expected, Object actual) { - if (expected == null) { - return actual == null; - } - - return isEquals(expected, actual); - } - - private static boolean isEquals(Object expected, Object actual) { - return expected.equals(actual); - } - - /** - * Asserts that two objects are equal. If they are not, an - * {@link AssertionError} without a message is thrown. If - * expected and actual are null, - * they are considered equal. - * - * @param expected expected value - * @param actual the value to check against expected - */ - static public void assertEquals(Object expected, Object actual) { - assertEquals(null, expected, actual); - } - - /** - * Asserts that two objects are not equals. If they are, an - * {@link AssertionError} is thrown with the given message. If - * first and second are null, - * they are considered equal. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param first first value to check - * @param second the value to check against first - */ - static public void assertNotEquals(String message, Object first, - Object second) { - if (equalsRegardingNull(first, second)) { - failEquals(message, first); - } - } - - /** - * Asserts that two objects are not equals. If they are, an - * {@link AssertionError} without a message is thrown. If - * first and second are null, - * they are considered equal. - * - * @param first first value to check - * @param second the value to check against first - */ - static public void assertNotEquals(Object first, Object second) { - assertNotEquals(null, first, second); - } - - private static void failEquals(String message, Object actual) { - String formatted = "Values should be different. "; - if (message != null) { - formatted = message + ". "; - } - - formatted += "Actual: " + actual; - fail(formatted); - } - - /** - * Asserts that two longs are not equals. If they are, an - * {@link AssertionError} is thrown with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param first first value to check - * @param second the value to check against first - */ - static public void assertNotEquals(String message, long first, long second) { - assertNotEquals(message, (Long) first, (Long) second); - } - - /** - * Asserts that two longs are not equals. If they are, an - * {@link AssertionError} without a message is thrown. - * - * @param first first value to check - * @param second the value to check against first - */ - static public void assertNotEquals(long first, long second) { - assertNotEquals(null, first, second); - } - - /** - * Asserts that two doubles or floats are not equal to within a positive delta. - * If they are, an {@link AssertionError} is thrown with the given - * message. If the expected value is infinity then the delta value is - * ignored. NaNs are considered equal: - * assertNotEquals(Double.NaN, Double.NaN, *) fails - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param first first value to check - * @param second the value to check against first - * @param delta the maximum delta between expected and - * actual for which both numbers are still - * considered equal. - */ - static public void assertNotEquals(String message, double first, - double second, double delta) { - if (!doubleIsDifferent(first, second, delta)) { - failEquals(message, new Double(first)); - } - } - - /** - * Asserts that two doubles or floats are not equal to within a positive delta. - * If they are, an {@link AssertionError} is thrown. If the expected - * value is infinity then the delta value is ignored.NaNs are considered - * equal: assertNotEquals(Double.NaN, Double.NaN, *) fails - * - * @param first first value to check - * @param second the value to check against first - * @param delta the maximum delta between expected and - * actual for which both numbers are still - * considered equal. - */ - static public void assertNotEquals(double first, double second, double delta) { - assertNotEquals(null, first, second, delta); - } - - /** - * Asserts that two object arrays are equal. If they are not, an - * {@link AssertionError} is thrown with the given message. If - * expecteds and actuals are null, - * they are considered equal. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expecteds Object array or array of arrays (multi-dimensional array) with - * expected values. - * @param actuals Object array or array of arrays (multi-dimensional array) with - * actual values - */ - public static void assertArrayEquals(String message, Object[] expecteds, - Object[] actuals) throws ArrayComparisonFailure { - internalArrayEquals(message, expecteds, actuals); - } - - /** - * Asserts that two object arrays are equal. If they are not, an - * {@link AssertionError} is thrown. If expected and - * actual are null, they are considered - * equal. - * - * @param expecteds Object array or array of arrays (multi-dimensional array) with - * expected values - * @param actuals Object array or array of arrays (multi-dimensional array) with - * actual values - */ - public static void assertArrayEquals(Object[] expecteds, Object[] actuals) { - assertArrayEquals(null, expecteds, actuals); - } - - /** - * Asserts that two byte arrays are equal. If they are not, an - * {@link AssertionError} is thrown with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expecteds byte array with expected values. - * @param actuals byte array with actual values - */ - public static void assertArrayEquals(String message, byte[] expecteds, - byte[] actuals) throws ArrayComparisonFailure { - internalArrayEquals(message, expecteds, actuals); - } - - /** - * Asserts that two byte arrays are equal. If they are not, an - * {@link AssertionError} is thrown. - * - * @param expecteds byte array with expected values. - * @param actuals byte array with actual values - */ - public static void assertArrayEquals(byte[] expecteds, byte[] actuals) { - assertArrayEquals(null, expecteds, actuals); - } - - /** - * Asserts that two char arrays are equal. If they are not, an - * {@link AssertionError} is thrown with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expecteds char array with expected values. - * @param actuals char array with actual values - */ - public static void assertArrayEquals(String message, char[] expecteds, - char[] actuals) throws ArrayComparisonFailure { - internalArrayEquals(message, expecteds, actuals); - } - - /** - * Asserts that two char arrays are equal. If they are not, an - * {@link AssertionError} is thrown. - * - * @param expecteds char array with expected values. - * @param actuals char array with actual values - */ - public static void assertArrayEquals(char[] expecteds, char[] actuals) { - assertArrayEquals(null, expecteds, actuals); - } - - /** - * Asserts that two short arrays are equal. If they are not, an - * {@link AssertionError} is thrown with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expecteds short array with expected values. - * @param actuals short array with actual values - */ - public static void assertArrayEquals(String message, short[] expecteds, - short[] actuals) throws ArrayComparisonFailure { - internalArrayEquals(message, expecteds, actuals); - } - - /** - * Asserts that two short arrays are equal. If they are not, an - * {@link AssertionError} is thrown. - * - * @param expecteds short array with expected values. - * @param actuals short array with actual values - */ - public static void assertArrayEquals(short[] expecteds, short[] actuals) { - assertArrayEquals(null, expecteds, actuals); - } - - /** - * Asserts that two int arrays are equal. If they are not, an - * {@link AssertionError} is thrown with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expecteds int array with expected values. - * @param actuals int array with actual values - */ - public static void assertArrayEquals(String message, int[] expecteds, - int[] actuals) throws ArrayComparisonFailure { - internalArrayEquals(message, expecteds, actuals); - } - - /** - * Asserts that two int arrays are equal. If they are not, an - * {@link AssertionError} is thrown. - * - * @param expecteds int array with expected values. - * @param actuals int array with actual values - */ - public static void assertArrayEquals(int[] expecteds, int[] actuals) { - assertArrayEquals(null, expecteds, actuals); - } - - /** - * Asserts that two long arrays are equal. If they are not, an - * {@link AssertionError} is thrown with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expecteds long array with expected values. - * @param actuals long array with actual values - */ - public static void assertArrayEquals(String message, long[] expecteds, - long[] actuals) throws ArrayComparisonFailure { - internalArrayEquals(message, expecteds, actuals); - } - - /** - * Asserts that two long arrays are equal. If they are not, an - * {@link AssertionError} is thrown. - * - * @param expecteds long array with expected values. - * @param actuals long array with actual values - */ - public static void assertArrayEquals(long[] expecteds, long[] actuals) { - assertArrayEquals(null, expecteds, actuals); - } - - /** - * Asserts that two double arrays are equal. If they are not, an - * {@link AssertionError} is thrown with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expecteds double array with expected values. - * @param actuals double array with actual values - */ - public static void assertArrayEquals(String message, double[] expecteds, - double[] actuals, double delta) throws ArrayComparisonFailure { - new InexactComparisonCriteria(delta).arrayEquals(message, expecteds, actuals); - } - - /** - * Asserts that two double arrays are equal. If they are not, an - * {@link AssertionError} is thrown. - * - * @param expecteds double array with expected values. - * @param actuals double array with actual values - */ - public static void assertArrayEquals(double[] expecteds, double[] actuals, double delta) { - assertArrayEquals(null, expecteds, actuals, delta); - } - - /** - * Asserts that two float arrays are equal. If they are not, an - * {@link AssertionError} is thrown with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expecteds float array with expected values. - * @param actuals float array with actual values - */ - public static void assertArrayEquals(String message, float[] expecteds, - float[] actuals, float delta) throws ArrayComparisonFailure { - new InexactComparisonCriteria(delta).arrayEquals(message, expecteds, actuals); - } - - /** - * Asserts that two float arrays are equal. If they are not, an - * {@link AssertionError} is thrown. - * - * @param expecteds float array with expected values. - * @param actuals float array with actual values - */ - public static void assertArrayEquals(float[] expecteds, float[] actuals, float delta) { - assertArrayEquals(null, expecteds, actuals, delta); - } - - /** - * Asserts that two object arrays are equal. If they are not, an - * {@link AssertionError} is thrown with the given message. If - * expecteds and actuals are null, - * they are considered equal. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expecteds Object array or array of arrays (multi-dimensional array) with - * expected values. - * @param actuals Object array or array of arrays (multi-dimensional array) with - * actual values - */ - private static void internalArrayEquals(String message, Object expecteds, - Object actuals) throws ArrayComparisonFailure { - new ExactComparisonCriteria().arrayEquals(message, expecteds, actuals); - } - - /** - * Asserts that two doubles are equal to within a positive delta. - * If they are not, an {@link AssertionError} is thrown with the given - * message. If the expected value is infinity then the delta value is - * ignored. NaNs are considered equal: - * assertEquals(Double.NaN, Double.NaN, *) passes - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expected expected value - * @param actual the value to check against expected - * @param delta the maximum delta between expected and - * actual for which both numbers are still - * considered equal. - */ - static public void assertEquals(String message, double expected, - double actual, double delta) { - if (doubleIsDifferent(expected, actual, delta)) { - failNotEquals(message, new Double(expected), new Double(actual)); - } - } - - /** - * Asserts that two floats are equal to within a positive delta. - * If they are not, an {@link AssertionError} is thrown with the given - * message. If the expected value is infinity then the delta value is - * ignored. NaNs are considered equal: - * assertEquals(Float.NaN, Float.NaN, *) passes - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expected expected value - * @param actual the value to check against expected - * @param delta the maximum delta between expected and - * actual for which both numbers are still - * considered equal. - */ - static public void assertEquals(String message, float expected, - float actual, float delta) { - if (Float.compare(expected, actual) == 0) { - return; - } - if (!(Math.abs(expected - actual) <= delta)) { - failNotEquals(message, new Float(expected), new Float(actual)); - } - } - - static private boolean doubleIsDifferent(double d1, double d2, double delta) { - if (Double.compare(d1, d2) == 0) { - return false; - } - return (Math.abs(d1 - d2) > delta); - - } - - /** - * Asserts that two longs are equal. If they are not, an - * {@link AssertionError} is thrown. - * - * @param expected expected long value. - * @param actual actual long value - */ - static public void assertEquals(long expected, long actual) { - if (expected != actual) - assertEquals(null, expected, actual); - } - - /** - * Asserts that two longs are equal. If they are not, an - * {@link AssertionError} is thrown with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expected long expected value. - * @param actual long actual value - */ - static public void assertEquals(String message, long expected, long actual) { - assertEquals(message, (Long) expected, (Long) actual); - } - - /** - * @deprecated Use - * assertEquals(double expected, double actual, double delta) - * instead - */ - @Deprecated(/* to be removed in x.22 */) -static public void assertEquals(double expected, double actual) { - assertEquals(null, expected, actual); - } - - /** - * @deprecated Use - * assertEquals(String message, double expected, double actual, double delta) - * instead - */ - @Deprecated(/* to be removed in x.22 */) -static public void assertEquals(String message, double expected, - double actual) { - fail("Use assertEquals(expected, actual, delta) to compare floating-point numbers"); - } - - /** - * Asserts that two doubles are equal to within a positive delta. - * If they are not, an {@link AssertionError} is thrown. If the expected - * value is infinity then the delta value is ignored.NaNs are considered - * equal: assertEquals(Double.NaN, Double.NaN, *) passes - * - * @param expected expected value - * @param actual the value to check against expected - * @param delta the maximum delta between expected and - * actual for which both numbers are still - * considered equal. - */ - static public void assertEquals(double expected, double actual, double delta) { - assertEquals(null, expected, actual, delta); - } - - /** - * Asserts that two floats are equal to within a positive delta. - * If they are not, an {@link AssertionError} is thrown. If the expected - * value is infinity then the delta value is ignored. NaNs are considered - * equal: assertEquals(Float.NaN, Float.NaN, *) passes - * - * @param expected expected value - * @param actual the value to check against expected - * @param delta the maximum delta between expected and - * actual for which both numbers are still - * considered equal. - */ - static public void assertEquals(float expected, float actual, float delta) { - assertEquals(null, expected, actual, delta); - } - - /** - * Asserts that an object isn't null. If it is an {@link AssertionError} is - * thrown with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param object Object to check or null - */ - static public void assertNotNull(String message, Object object) { - assertTrue(message, object != null); - } - - /** - * Asserts that an object isn't null. If it is an {@link AssertionError} is - * thrown. - * - * @param object Object to check or null - */ - static public void assertNotNull(Object object) { - assertNotNull(null, object); - } - - /** - * Asserts that an object is null. If it is not, an {@link AssertionError} - * is thrown with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param object Object to check or null - */ - static public void assertNull(String message, Object object) { - if (object == null) { - return; - } - failNotNull(message, object); - } - - /** - * Asserts that an object is null. If it isn't an {@link AssertionError} is - * thrown. - * - * @param object Object to check or null - */ - static public void assertNull(Object object) { - assertNull(null, object); - } - - static private void failNotNull(String message, Object actual) { - String formatted = ""; - if (message != null) { - formatted = message + " "; - } - fail(formatted + "expected null, but was:<" + actual + ">"); - } - - /** - * Asserts that two objects refer to the same object. If they are not, an - * {@link AssertionError} is thrown with the given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expected the expected object - * @param actual the object to compare to expected - */ - static public void assertSame(String message, Object expected, Object actual) { - if (expected == actual) { - return; - } - failNotSame(message, expected, actual); - } - - /** - * Asserts that two objects refer to the same object. If they are not the - * same, an {@link AssertionError} without a message is thrown. - * - * @param expected the expected object - * @param actual the object to compare to expected - */ - static public void assertSame(Object expected, Object actual) { - assertSame(null, expected, actual); - } - - /** - * Asserts that two objects do not refer to the same object. If they do - * refer to the same object, an {@link AssertionError} is thrown with the - * given message. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param unexpected the object you don't expect - * @param actual the object to compare to unexpected - */ - static public void assertNotSame(String message, Object unexpected, - Object actual) { - if (unexpected == actual) { - failSame(message); - } - } - - /** - * Asserts that two objects do not refer to the same object. If they do - * refer to the same object, an {@link AssertionError} without a message is - * thrown. - * - * @param unexpected the object you don't expect - * @param actual the object to compare to unexpected - */ - static public void assertNotSame(Object unexpected, Object actual) { - assertNotSame(null, unexpected, actual); - } - - static private void failSame(String message) { - String formatted = ""; - if (message != null) { - formatted = message + " "; - } - fail(formatted + "expected not same"); - } - - static private void failNotSame(String message, Object expected, - Object actual) { - String formatted = ""; - if (message != null) { - formatted = message + " "; - } - fail(formatted + "expected same:<" + expected + "> was not:<" + actual - + ">"); - } - - static private void failNotEquals(String message, Object expected, - Object actual) { - fail(format(message, expected, actual)); - } - - static String format(String message, Object expected, Object actual) { - String formatted = ""; - if (message != null && !message.equals("")) { - formatted = message + " "; - } - String expectedString = String.valueOf(expected); - String actualString = String.valueOf(actual); - if (expectedString.equals(actualString)) { - return formatted + "expected: " - + formatClassAndValue(expected, expectedString) - + " but was: " + formatClassAndValue(actual, actualString); - - } else { - return formatted + "expected:<" + expectedString + "> but was:<" - + actualString + ">"; - } - } - - private static String formatClassAndValue(Object value, String valueString) { - String className = value == null ? "null" : value.getClass().getName(); - return className + "<" + valueString + ">"; - } - - /** - * Asserts that two object arrays are equal. If they are not, an - * {@link AssertionError} is thrown with the given message. If - * expecteds and actuals are null, - * they are considered equal. - * - * @param message the identifying message for the {@link AssertionError} (null - * okay) - * @param expecteds Object array or array of arrays (multi-dimensional array) with - * expected values. - * @param actuals Object array or array of arrays (multi-dimensional array) with - * actual values - * @deprecated use assertArrayEquals - */ - @Deprecated(/* to be removed in x.22 */) -public static void assertEquals(String message, Object[] expecteds, - Object[] actuals) { - assertArrayEquals(message, expecteds, actuals); - } - - /** - * Asserts that two object arrays are equal. If they are not, an - * {@link AssertionError} is thrown. If expected and - * actual are null, they are considered - * equal. - * - * @param expecteds Object array or array of arrays (multi-dimensional array) with - * expected values - * @param actuals Object array or array of arrays (multi-dimensional array) with - * actual values - * @deprecated use assertArrayEquals - */ - @Deprecated(/* to be removed in x.22 */) -public static void assertEquals(Object[] expecteds, Object[] actuals) { - assertArrayEquals(expecteds, actuals); - } - - /** - * Asserts that actual satisfies the condition specified by - * matcher. If not, an {@link AssertionError} is thrown with - * information about the matcher and failing value. Example: - *

    - *

    -     *   assertThat(0, is(1)); // fails:
    -     *     // failure message:
    -     *     // expected: is <1>
    -     *     // got value: <0>
    -     *   assertThat(0, is(not(1))) // passes
    -     * 
    - *

    - * org.hamcrest.Matcher does not currently document the meaning - * of its type parameter T. This method assumes that a matcher - * typed as Matcher<T> can be meaningfully applied only - * to values that could be assigned to a variable of type T. - * - * @param the static type accepted by the matcher (this can flag obvious - * compile-time problems such as {@code assertThat(1, is("a"))} - * @param actual the computed value being compared - * @param matcher an expression, built of {@link Matcher}s, specifying allowed - * values - * @see org.hamcrest.CoreMatchers - * @see org.hamcrest.MatcherAssert - */ - public static void assertThat(T actual, Matcher matcher) { - assertThat("", actual, matcher); - } - - /** - * Asserts that actual satisfies the condition specified by - * matcher. If not, an {@link AssertionError} is thrown with - * the reason and information about the matcher and failing value. Example: - *

    - *

    -     *   assertThat("Help! Integers don't work", 0, is(1)); // fails:
    -     *     // failure message:
    -     *     // Help! Integers don't work
    -     *     // expected: is <1>
    -     *     // got value: <0>
    -     *   assertThat("Zero is one", 0, is(not(1))) // passes
    -     * 
    - *

    - * org.hamcrest.Matcher does not currently document the meaning - * of its type parameter T. This method assumes that a matcher - * typed as Matcher<T> can be meaningfully applied only - * to values that could be assigned to a variable of type T. - * - * @param reason additional information about the error - * @param the static type accepted by the matcher (this can flag obvious - * compile-time problems such as {@code assertThat(1, is("a"))} - * @param actual the computed value being compared - * @param matcher an expression, built of {@link Matcher}s, specifying allowed - * values - * @see org.hamcrest.CoreMatchers - * @see org.hamcrest.MatcherAssert - */ - public static void assertThat(String reason, T actual, - Matcher matcher) { - MatcherAssert.assertThat(reason, actual, matcher); - } -} diff --git a/affinity/src/test/java/software/chronicle/enterprise/internals/JnaAffinityTest.java b/affinity/src/test/java/software/chronicle/enterprise/internals/JnaAffinityTest.java index d9a723019..a65863c1d 100644 --- a/affinity/src/test/java/software/chronicle/enterprise/internals/JnaAffinityTest.java +++ b/affinity/src/test/java/software/chronicle/enterprise/internals/JnaAffinityTest.java @@ -1,22 +1,9 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package software.chronicle.enterprise.internals; +import net.openhft.affinity.BaseAffinityTest; import net.openhft.affinity.IAffinity; import net.openhft.affinity.impl.LinuxJNAAffinity; import net.openhft.affinity.impl.Utilities; @@ -27,15 +14,14 @@ import java.util.BitSet; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * @author peter.lawrey */ -public class JnaAffinityTest { - protected static final int CORES = Runtime.getRuntime().availableProcessors(); - protected static final BitSet CORES_MASK = new BitSet(CORES); +public class JnaAffinityTest extends BaseAffinityTest { + private static final int CORES = Runtime.getRuntime().availableProcessors(); + private static final BitSet CORES_MASK = new BitSet(CORES); static { CORES_MASK.set(0, CORES, true); @@ -54,10 +40,7 @@ public void getAffinityCompletesGracefully() { @Test public void getAffinityReturnsValidValue() { final BitSet affinity = getImpl().getAffinity(); - assertTrue( - "Affinity mask " + Utilities.toBinaryString(affinity) + " must be non-empty", - !affinity.isEmpty() - ); + assertFalse("Affinity mask " + Utilities.toBinaryString(affinity) + " must be non-empty", affinity.isEmpty()); final int allCoresMask = (1 << CORES) - 1; assertTrue( "Affinity mask " + Utilities.toBinaryString(affinity) + " must be <=(2^" + CORES + "-1 = " + allCoresMask + ")", @@ -80,8 +63,7 @@ public void getAffinityReturnsValuePreviouslySet() { return; } final IAffinity impl = LinuxJNAAffinity.INSTANCE; - final int cores = CORES; - for (int core = 0; core < cores; core++) { + for (int core = 0; core < CORES; core++) { final BitSet mask = new BitSet(); mask.set(core, true); getAffinityReturnsValuePreviouslySet(impl, mask); @@ -108,7 +90,7 @@ public void tearDown() { getImpl().setAffinity(CORES_MASK); } - public IAffinity getImpl() { + private IAffinity getImpl() { return LinuxJNAAffinity.INSTANCE; } } diff --git a/affinity/src/test/java/software/chronicle/enterprise/internals/NativeAffinityTest.java b/affinity/src/test/java/software/chronicle/enterprise/internals/NativeAffinityTest.java index 61991595d..fa2bc7fba 100644 --- a/affinity/src/test/java/software/chronicle/enterprise/internals/NativeAffinityTest.java +++ b/affinity/src/test/java/software/chronicle/enterprise/internals/NativeAffinityTest.java @@ -1,22 +1,9 @@ /* - * Copyright 2016-2020 chronicle.software - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 */ - package software.chronicle.enterprise.internals; +import net.openhft.affinity.BaseAffinityTest; import net.openhft.affinity.IAffinity; import net.openhft.affinity.impl.LinuxJNAAffinity; import net.openhft.affinity.impl.Utilities; @@ -25,15 +12,14 @@ import java.util.BitSet; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * @author peter.lawrey */ -public class NativeAffinityTest { - protected static final int CORES = Runtime.getRuntime().availableProcessors(); - protected static final BitSet CORES_MASK = new BitSet(CORES); +public class NativeAffinityTest extends BaseAffinityTest { + private static final int CORES = Runtime.getRuntime().availableProcessors(); + private static final BitSet CORES_MASK = new BitSet(CORES); static { CORES_MASK.set(0, CORES, true); @@ -52,10 +38,7 @@ public void getAffinityCompletesGracefully() { @Test public void getAffinityReturnsValidValue() { final BitSet affinity = getImpl().getAffinity(); - assertTrue( - "Affinity mask " + Utilities.toBinaryString(affinity) + " must be non-empty", - !affinity.isEmpty() - ); + assertFalse("Affinity mask " + Utilities.toBinaryString(affinity) + " must be non-empty", affinity.isEmpty()); final int allCoresMask = (1 << CORES) - 1; assertTrue( "Affinity mask " + Utilities.toBinaryString(affinity) + " must be <=(2^" + CORES + "-1 = " + allCoresMask + ")", @@ -79,8 +62,7 @@ public void getAffinityReturnsValuePreviouslySet() { return; } final IAffinity impl = NativeAffinity.INSTANCE; - final int cores = CORES; - for (int core = 0; core < cores; core++) { + for (int core = 0; core < CORES; core++) { final BitSet mask = new BitSet(); mask.set(core, true); getAffinityReturnsValuePreviouslySet(impl, mask); @@ -134,7 +116,7 @@ public void tearDown() { getImpl().setAffinity(CORES_MASK); } - public IAffinity getImpl() { + private IAffinity getImpl() { return NativeAffinity.INSTANCE; } } diff --git a/affinity/src/test/resources/dual.E5405.properties b/affinity/src/test/resources/dual.E5405.properties new file mode 100644 index 000000000..d656b7376 --- /dev/null +++ b/affinity/src/test/resources/dual.E5405.properties @@ -0,0 +1,24 @@ +# +# Copyright 2016-2025 chronicle.software +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +0=0,0,0 +1=0,1,0 +2=0,2,0 +3=0,3,0 +4=1,4,0 +5=1,5,0 +6=1,6,0 +7=1,7,0 diff --git a/affinity/src/test/resources/dual.xeon.properties b/affinity/src/test/resources/dual.xeon.properties new file mode 100644 index 000000000..36fc4ef90 --- /dev/null +++ b/affinity/src/test/resources/dual.xeon.properties @@ -0,0 +1,20 @@ +# +# Copyright 2016-2025 chronicle.software +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +0=0,0,0 +1=0,0,1 +2=3,3,0 +3=3,3,1 diff --git a/affinity/src/test/resources/i3.properties b/affinity/src/test/resources/i3.properties new file mode 100644 index 000000000..ab4e83876 --- /dev/null +++ b/affinity/src/test/resources/i3.properties @@ -0,0 +1,20 @@ +# +# Copyright 2016-2025 chronicle.software +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +0=0,0,0 +1=0,2,0 +2=0,0,1 +3=0,2,1 diff --git a/affinity/src/test/resources/i7.properties b/affinity/src/test/resources/i7.properties index 20191b55d..9e617cf9c 100644 --- a/affinity/src/test/resources/i7.properties +++ b/affinity/src/test/resources/i7.properties @@ -1,5 +1,5 @@ # -# Copyright 2016 higherfrequencytrading.com +# Copyright 2016-2025 chronicle.software # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/images/Thread-Affinity_line.png b/docs/images/Thread-Affinity_line.png similarity index 100% rename from images/Thread-Affinity_line.png rename to docs/images/Thread-Affinity_line.png diff --git a/pom.xml b/pom.xml index 7682635bb..f88f45845 100644 --- a/pom.xml +++ b/pom.xml @@ -1,32 +1,22 @@ + + Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + +--> 4.0.0 net.openhft - root-parent-pom - 1.2.1 + java-parent-pom + 2026.0 Java-Thread-Affinity - 3.21ea2-SNAPSHOT + 2026.3-SNAPSHOT pom OpenHFT/Java-Thread-Affinity Parent @@ -42,7 +32,7 @@ scm:git:git@github.com:OpenHFT/Java-Thread-Affinity.git scm:git:git@github.com:OpenHFT/Java-Thread-Affinity.git - ea + ea