This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-exec.git

commit 80eca0445987a0297258ad4f8c143fc4017ab2d3
Author: Gary D. Gregory <garydgreg...@gmail.com>
AuthorDate: Wed Apr 30 10:01:26 2025 -0400

    Fix SpotBugs AT_STALE_THREAD_WRITE_OF_PRIMITIVE: Shared primitive
    variable "added" in one thread may not yield the value of the most
    recent write from another thread
    [org.apache.commons.exec.ShutdownHookProcessDestroyer]
---
 src/changes/changes.xml                             |  3 ++-
 .../commons/exec/ShutdownHookProcessDestroyer.java  | 21 ++++++++++-----------
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index a70843a3..a2d7da1c 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -42,7 +42,8 @@
             <action type="fix" dev="ggregory" due-to="Gary Gregory">Deprecate 
MapUtils.MapUtils().</action>
             <action type="fix" dev="ggregory" due-to="Gary Gregory">Deprecate 
StringUtils.StringUtils().</action>
             <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix 
Javadoc warnings.</action>
-            <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix 
SpotBugs AT_STALE_THREAD_WRITE_OF_PRIMITIVE: Shared primitive variable 
"shouldDestroy" in one thread may not yield the value of the most recent write 
from another thread 
[org.apache.commons.exec.ShutdownHookProcessDestroyer$ProcessDestroyerThread].</action>
+            <action type="fix" dev="ggregory" due-to="Gary Gregory, 
SpotBugs">Fix SpotBugs AT_STALE_THREAD_WRITE_OF_PRIMITIVE: Shared primitive 
variable "shouldDestroy" in one thread may not yield the value of the most 
recent write from another thread 
[org.apache.commons.exec.ShutdownHookProcessDestroyer$ProcessDestroyerThread].</action>
+            <action type="fix" dev="ggregory" due-to="Gary Gregory, 
SpotBugs">Fix SpotBugs AT_STALE_THREAD_WRITE_OF_PRIMITIVE: Shared primitive 
variable "added" in one thread may not yield the value of the most recent write 
from another thread 
[org.apache.commons.exec.ShutdownHookProcessDestroyer].</action>
             <!-- UPDATE -->
             <action type="update" dev="ggregory" due-to="Dependabot, Gary 
Gregory">Bump org.apache.commons:commons-parent from 65 to 81 #174, #204, #212, 
#214, #219, #223, #226, #233, #253.</action>
         </release>
diff --git 
a/src/main/java/org/apache/commons/exec/ShutdownHookProcessDestroyer.java 
b/src/main/java/org/apache/commons/exec/ShutdownHookProcessDestroyer.java
index 49e337df..b8879389 100644
--- a/src/main/java/org/apache/commons/exec/ShutdownHookProcessDestroyer.java
+++ b/src/main/java/org/apache/commons/exec/ShutdownHookProcessDestroyer.java
@@ -41,7 +41,7 @@ public class ShutdownHookProcessDestroyer implements 
ProcessDestroyer, Runnable
         }
 
         public void setShouldDestroy(final boolean shouldDestroy) {
-            this.shouldDestroy.set(shouldDestroy);
+            this.shouldDestroy.compareAndSet(!shouldDestroy, shouldDestroy);
         }
     }
 
@@ -52,12 +52,12 @@ public class ShutdownHookProcessDestroyer implements 
ProcessDestroyer, Runnable
     private ProcessDestroyerThread destroyProcessThread;
 
     /** Whether or not this ProcessDestroyer has been registered as a shutdown 
hook. */
-    private boolean added;
+    private AtomicBoolean added = new AtomicBoolean();
 
     /**
      * Whether or not this ProcessDestroyer is currently running as shutdown 
hook.
      */
-    private volatile boolean running;
+    private AtomicBoolean running = new AtomicBoolean();
 
     /**
      * Constructs a {@code ProcessDestroyer} and obtains {@code 
Runtime.addShutdownHook()} and {@code Runtime.removeShutdownHook()} through 
reflection. The
@@ -90,10 +90,10 @@ public class ShutdownHookProcessDestroyer implements 
ProcessDestroyer, Runnable
      * Registers this {@code ProcessDestroyer} as a shutdown hook.
      */
     private void addShutdownHook() {
-        if (!running) {
+        if (!running.get()) {
             destroyProcessThread = new ProcessDestroyerThread();
             Runtime.getRuntime().addShutdownHook(destroyProcessThread);
-            added = true;
+            added.compareAndSet(false, true);
         }
     }
 
@@ -103,7 +103,7 @@ public class ShutdownHookProcessDestroyer implements 
ProcessDestroyer, Runnable
      * @return true if this is currently added as shutdown hook.
      */
     public boolean isAddedAsShutdownHook() {
-        return added;
+        return added.get();
     }
 
     /**
@@ -134,17 +134,16 @@ public class ShutdownHookProcessDestroyer implements 
ProcessDestroyer, Runnable
     }
 
     /**
-     * Removes this {@code ProcessDestroyer} as a shutdown hook, uses 
reflection to ensure pre-JDK 1.3 compatibility
+     * Removes this {@code ProcessDestroyer} as a shutdown hook.
      */
     private void removeShutdownHook() {
-        if (added && !running) {
+        if (added.get() && !running.get()) {
             final boolean removed = 
Runtime.getRuntime().removeShutdownHook(destroyProcessThread);
             if (!removed) {
                 System.err.println("Could not remove shutdown hook");
             }
             // start the hook thread, a unstarted thread may not be eligible 
for garbage collection Cf.: https://developer.java.sun.com/developer/
             // bugParade/bugs/4533087.html
-
             destroyProcessThread.setShouldDestroy(false);
             destroyProcessThread.start();
             // this should return quickly, since it basically is a NO-OP.
@@ -155,7 +154,7 @@ public class ShutdownHookProcessDestroyer implements 
ProcessDestroyer, Runnable
                 // it should not kill any processes unexpectedly
             }
             destroyProcessThread = null;
-            added = false;
+            added.compareAndSet(true, false);
         }
     }
 
@@ -165,7 +164,7 @@ public class ShutdownHookProcessDestroyer implements 
ProcessDestroyer, Runnable
     @Override
     public void run() {
         synchronized (processes) {
-            running = true;
+            running.compareAndSet(false, true);
             processes.forEach(process -> {
                 try {
                     process.destroy();

Reply via email to