diff --git a/jpos/src/main/java/org/jpos/space/TSpace.java b/jpos/src/main/java/org/jpos/space/TSpace.java index 14ef344d90..955af6cb49 100644 --- a/jpos/src/main/java/org/jpos/space/TSpace.java +++ b/jpos/src/main/java/org/jpos/space/TSpace.java @@ -1,6 +1,6 @@ /* * jPOS Project [http://jpos.org] - * Copyright (C) 2000-2021 jPOS Software SRL + * Copyright (C) 2000-2023 jPOS Software SRL * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,11 +17,10 @@ */ package org.jpos.space; -import org.jpos.util.Loggeable; +import org.jpos.util.Loggeable; import java.io.PrintStream; import java.io.Serializable; -import java.time.Duration; import java.util.*; import java.util.concurrent.TimeUnit; @@ -40,8 +39,9 @@ public class TSpace implements LocalSpace, Loggeable, Runnable { private static final long GCLONG = 60*1000; private static final long NRD_RESOLUTION = 500L; private static final int MAX_ENTRIES_IN_DUMP = 1000; + private static final long ONE_MILLION = 1_000_000L; // multiplier millis --> nanos private final Set[] expirables; - private Duration lastLongGC = Duration.ofNanos(System.nanoTime()); + private long lastLongGC = System.nanoTime(); public TSpace () { super(); @@ -70,7 +70,7 @@ public void out (K key, V value, long timeout) { throw new NullPointerException ("key=" + key + ", value=" + value); Object v = value; if (timeout > 0) { - v = new Expirable (value, Duration.ofNanos(System.nanoTime()).plus(Duration.ofMillis(timeout))); + v = new Expirable (value, System.nanoTime() + (timeout * ONE_MILLION)); } synchronized (this) { List l = getList(key); @@ -112,18 +112,18 @@ public synchronized V in (Object key) { @Override public synchronized V in (Object key, long timeout) { - Object obj; - Duration to = Duration.ofMillis(timeout); - Duration now = Duration.ofNanos(System.nanoTime()); - Duration duration; - while ((obj = inp (key)) == null && - to.compareTo(duration = Duration.ofNanos(System.nanoTime()).minus(now)) > 0) + V obj; + long now = System.nanoTime(); + long to = now + timeout * ONE_MILLION; + long waitFor; + while ( (obj = inp (key)) == null && + (waitFor = (to - System.nanoTime())) >= 0 ) { try { - this.wait (Math.max(to.minus(duration).toMillis(), 1L)); + this.wait(Math.max(waitFor / ONE_MILLION, 1L)); } catch (InterruptedException e) { } } - return (V) obj; + return obj; } @Override @@ -139,18 +139,18 @@ public synchronized V rd (Object key) { @Override public synchronized V rd (Object key, long timeout) { - Object obj; - Duration to = Duration.ofMillis(timeout); - Duration now = Duration.ofNanos(System.nanoTime()); - Duration duration; - while ((obj = rdp (key)) == null && - to.compareTo(duration = Duration.ofNanos(System.nanoTime()).minus(now)) > 0) + V obj; + long now = System.nanoTime(); + long to = now + (timeout * ONE_MILLION); + long waitFor; + while ( (obj = rdp (key)) == null && + (waitFor = (to - System.nanoTime())) >= 0 ) { try { - this.wait (Math.max(to.minus(duration).toMillis(), 1L)); + this.wait(Math.max(waitFor / ONE_MILLION, 1L)); } catch (InterruptedException e) { } } - return (V) obj; + return obj; } @Override @@ -164,18 +164,19 @@ public synchronized void nrd (Object key) { @Override public synchronized V nrd (Object key, long timeout) { - Object obj; - Duration to = Duration.ofMillis(timeout); - Duration now = Duration.ofNanos(System.nanoTime()); - Duration duration; - while ((obj = rdp (key)) != null && - to.compareTo(duration = Duration.ofNanos(System.nanoTime()).minus(now)) > 0) + V obj; + long now = System.nanoTime(); + long to = now + (timeout * ONE_MILLION); + long waitFor; + while ( (obj = rdp (key)) != null && + (waitFor = (to - System.nanoTime())) >= 0 ) { try { - this.wait (Math.min(NRD_RESOLUTION, Math.max(to.minus(duration).toMillis(), 1L))); + this.wait(Math.min(NRD_RESOLUTION, + Math.max(waitFor / ONE_MILLION, 1L))); } catch (InterruptedException ignored) { } } - return (V) obj; + return obj; } @Override @@ -189,9 +190,9 @@ public void run () { public void gc () { gc(0); - if (Duration.ofMillis(GCLONG).compareTo(Duration.ofNanos(System.nanoTime()).minus(lastLongGC)) > 0) { + if (System.nanoTime() - lastLongGC > GCLONG) { gc(1); - lastLongGC = Duration.ofNanos(System.nanoTime()); + lastLongGC = System.nanoTime(); } } @@ -339,7 +340,7 @@ public void push (K key, V value, long timeout) { throw new NullPointerException ("key=" + key + ", value=" + value); Object v = value; if (timeout > 0) { - v = new Expirable (value, Duration.ofNanos(System.nanoTime()).plus(Duration.ofMillis(timeout))); + v = new Expirable (value, System.nanoTime() + (timeout * ONE_MILLION)); } synchronized (this) { List l = getList(key); @@ -376,7 +377,7 @@ public void put (K key, V value, long timeout) { throw new NullPointerException ("key=" + key + ", value=" + value); Object v = value; if (timeout > 0) { - v = new Expirable (value, Duration.ofNanos(System.nanoTime()).plus(Duration.ofMillis(timeout))); + v = new Expirable (value, System.nanoTime() + (timeout * ONE_MILLION)); } synchronized (this) { List l = new LinkedList(); @@ -402,15 +403,15 @@ public boolean existAny (K[] keys) { @Override public boolean existAny (K[] keys, long timeout) { - Duration to = Duration.ofMillis(timeout); - Duration now = Duration.ofNanos(System.nanoTime()); - Duration duration; - while (to.compareTo(duration = Duration.ofNanos(System.nanoTime()).minus(now)) > 0) { + long now = System.nanoTime(); + long to = now + (timeout * ONE_MILLION); + long waitFor; + while ((waitFor = (to - System.nanoTime())) >= 0) { if (existAny (keys)) return true; synchronized (this) { try { - wait (Math.max(to.minus(duration).toMillis(), 1L)); + this.wait(Math.max(waitFor / ONE_MILLION, 1L)); } catch (InterruptedException e) { } } } @@ -521,19 +522,24 @@ private void unregisterExpirable(Object k) { static class Expirable implements Comparable, Serializable { - static final long serialVersionUID = 0xA7F22BF5; + private static final long serialVersionUID = 0xA7F22BF5; Object value; - Duration expires; - public Expirable (Object value, Duration expires) { + /** + * When to expire, in the future, as given by monotonic System.nanoTime().
+ * IMPORTANT: always use a nanosec offset from System.nanoTime()! + */ + long expires; + + Expirable (Object value, long expires) { super(); this.value = value; this.expires = expires; } - public boolean isExpired () { - return expires.compareTo(Duration.ofNanos(System.nanoTime())) < 0; + boolean isExpired () { + return (System.nanoTime() - expires) > 0; } @Override @@ -544,20 +550,16 @@ public String toString() { + ",expired=" + isExpired (); } - public Object getValue() { + Object getValue() { return isExpired() ? null : value; } @Override - public int compareTo (Object obj) { - Expirable other = (Expirable) obj; - Duration otherExpires = other.expires; - if (otherExpires.equals(expires)) - return 0; - else if (expires.compareTo(otherExpires) < 0) - return -1; - else - return 1; + public int compareTo (Object other) { + long diff = this.expires - ((Expirable)other).expires; + return diff > 0 ? 1 : + diff < 0 ? -1 : + 0; } } diff --git a/jpos/src/test/java/org/jpos/space/TSpaceTest.java b/jpos/src/test/java/org/jpos/space/TSpaceTest.java index 4ddf9aaf5c..61a66ceb89 100644 --- a/jpos/src/test/java/org/jpos/space/TSpaceTest.java +++ b/jpos/src/test/java/org/jpos/space/TSpaceTest.java @@ -43,6 +43,7 @@ @SuppressWarnings("unchecked") @ExtendWith(MockitoExtension.class) public class TSpaceTest { + static final long EXPIRE_OFFSET = Duration.ofDays(9999).toNanos(); @Test public void testConstructor() throws Throwable { @@ -63,27 +64,27 @@ public void testDump() throws Throwable { @Test public void testExpirableCompareTo() throws Throwable { - int result = new TSpace.Expirable(Integer.valueOf(0), Duration.ofMillis(1L)).compareTo(new TSpace.Expirable(new Object(), Duration.ofMillis(0L))); + int result = new TSpace.Expirable(Integer.valueOf(0), 1L).compareTo(new TSpace.Expirable(new Object(), 0L)); assertEquals(1, result, "result"); } @Test public void testExpirableCompareTo1() throws Throwable { - TSpace.Expirable obj = new TSpace.Expirable(new Object(), Duration.ofMillis(0L)); - int result = new TSpace.Expirable(new Object(), Duration.ofMillis(0L)).compareTo(obj); + TSpace.Expirable obj = new TSpace.Expirable(new Object(), 0L); + int result = new TSpace.Expirable(new Object(), 0L).compareTo(obj); assertEquals(0, result, "result"); } @Test public void testExpirableCompareTo2() throws Throwable { - int result = new TSpace.Expirable(null, Duration.ofMillis(0L)).compareTo(new TSpace.Expirable(new Object(), Duration.ofMillis(1L))); + int result = new TSpace.Expirable(null, 0L).compareTo(new TSpace.Expirable(new Object(), 1L)); assertEquals(-1, result, "result"); } @Test public void testExpirableCompareToThrowsNullPointerException() throws Throwable { try { - new TSpace.Expirable(new Object(), Duration.ofMillis(100L)).compareTo(null); + new TSpace.Expirable(new Object(), 100L).compareTo(null); fail("Expected NullPointerException to be thrown"); } catch (NullPointerException ex) { if (isJavaVersionAtMost(JAVA_14)) { @@ -97,51 +98,53 @@ public void testExpirableCompareToThrowsNullPointerException() throws Throwable @Test public void testExpirableConstructor() throws Throwable { Object value = new Object(); - TSpace.Expirable expirable = new TSpace.Expirable(value, Duration.ofMillis(100L)); - assertEquals(Duration.ofMillis(100L), expirable.expires, "expirable.expires"); + TSpace.Expirable expirable = new TSpace.Expirable(value, 100L); + assertEquals(100L, expirable.expires, "expirable.expires"); assertSame(value, expirable.value, "expirable.value"); } @Test public void testExpirableGetValue() throws Throwable { - String result = (String) new TSpace.Expirable("", Duration.ofNanos(System.nanoTime()).plus(Duration.ofMillis(9184833384926L))).getValue(); + String result = (String) new TSpace.Expirable("", System.nanoTime() + EXPIRE_OFFSET).getValue(); assertEquals("", result, "result"); } @Test public void testExpirableGetValue1() throws Throwable { - Object result = new TSpace.Expirable(null, Duration.ofMillis(9184833384926L)).getValue(); + Object result = new TSpace.Expirable(null, System.nanoTime() + EXPIRE_OFFSET).getValue(); assertNull(result, "result"); } @Test public void testExpirableGetValue2() throws Throwable { - Object result = new TSpace.Expirable(new Object(), Duration.ofNanos(System.nanoTime()).minus(Duration.ofMillis(100L))).getValue(); + // using negative offset to ensure expiration (literally, object is born already expired) + Object result = new TSpace.Expirable(new Object(), System.nanoTime() - EXPIRE_OFFSET).getValue(); assertNull(result, "result"); } @Test public void testExpirableIsExpired() throws Throwable { - boolean result = new TSpace.Expirable("", Duration.ofNanos(System.nanoTime()).plus(Duration.ofMillis(9184833384926L))).isExpired(); + boolean result = new TSpace.Expirable("", System.nanoTime() + EXPIRE_OFFSET).isExpired(); assertFalse(result, "result"); } @Test public void testExpirableIsExpired1() throws Throwable { - boolean result = new TSpace.Expirable(new Object(), Duration.ofNanos(System.nanoTime()).minus(Duration.ofMillis(100L))).isExpired(); + // using negative offset to ensure expiration (literally, object is born already expired) + boolean result = new TSpace.Expirable(new Object(), System.nanoTime() - EXPIRE_OFFSET).isExpired(); assertTrue(result, "result"); } @Test public void testExpirableToString() throws Throwable { - new TSpace.Expirable(";\"i", Duration.ofMillis(100L)).toString(); + new TSpace.Expirable(";\"i", 100L).toString(); assertTrue(true, "Test completed without Exception"); } @Test public void testExpirableToStringThrowsNullPointerException() throws Throwable { try { - new TSpace.Expirable(null, Duration.ofMillis(100L)).toString(); + new TSpace.Expirable(null,100L).toString(); fail("Expected NullPointerException to be thrown"); } catch (NullPointerException ex) { if (isJavaVersionAtMost(JAVA_14)) { @@ -154,7 +157,6 @@ public void testExpirableToStringThrowsNullPointerException() throws Throwable { @Test public void testGc() throws Throwable { - TSpace tSpace = new TSpace(); tSpace.gc(); assertEquals(0, tSpace.entries.size(), "tSpace.entries.size()"); @@ -208,6 +210,7 @@ public void testInp1() throws Throwable { "tSpace.entries.get(\"\").get(0) had \"testString\" removed"); assertEquals("testString", result, "result"); assertEquals(3, tSpace.entries.size(), "tSpace.entries.size()"); + tSpace.dump(System.out, ">>"); } @Test @@ -240,12 +243,10 @@ public void testNotifyReaders() { final Space sp = new TSpace(); final AtomicInteger ai = new AtomicInteger(10); for (int i=0; i<10; i++) { - new Thread() { - public void run() { - if (sp.rd("TEST", 5000L) != null) - ai.decrementAndGet(); - } - }.start(); + new Thread(()->{ + if (sp.rd("TEST", 5000L) != null) + ai.decrementAndGet(); + }).start(); } sp.out("TEST", Boolean.TRUE); ISOUtil.sleep(500L);