In src/main/java/co/stateful/Atomic.java, call() registers a JVM shutdown hook on line 132, before acquiring the lock. The hook on lines 123-131 unconditionally invokes this.lock.unlock(this.label) and ignores the this.locked flag, which is only set to true on line 160 after the acquisition loop succeeds.
If the JVM terminates while the acquisition loop on lines 135-159 is still waiting — DEFAULT_MAX allows up to five minutes there — the hook issues an unlock for a lock this code never held; with the default empty label set by the two-argument constructor on line 90, the server may match the unlock against another holder's empty label and release that holder's lock. Similarly, if this.lock.lock(this.label) or TimeUnit.MILLISECONDS.sleep(delay) throws before line 160, the exception bypasses the try/finally on lines 161-175, so removeShutdownHook(hook) is never called and the hook stays installed for the rest of the JVM's life, then fires a spurious unlock at shutdown.
Guard the hook body with a check on this.locked.get() before calling unlock, or register the hook only after this.locked.set(true) and remove it in a finally that wraps the acquisition loop as well.
In
src/main/java/co/stateful/Atomic.java,call()registers a JVM shutdown hook on line 132, before acquiring the lock. The hook on lines 123-131 unconditionally invokesthis.lock.unlock(this.label)and ignores thethis.lockedflag, which is only set to true on line 160 after the acquisition loop succeeds.If the JVM terminates while the acquisition loop on lines 135-159 is still waiting —
DEFAULT_MAXallows up to five minutes there — the hook issues anunlockfor a lock this code never held; with the default empty label set by the two-argument constructor on line 90, the server may match the unlock against another holder's empty label and release that holder's lock. Similarly, ifthis.lock.lock(this.label)orTimeUnit.MILLISECONDS.sleep(delay)throws before line 160, the exception bypasses thetry/finallyon lines 161-175, soremoveShutdownHook(hook)is never called and the hook stays installed for the rest of the JVM's life, then fires a spuriousunlockat shutdown.Guard the hook body with a check on
this.locked.get()before callingunlock, or register the hook only afterthis.locked.set(true)and remove it in a finally that wraps the acquisition loop as well.