Skip to content

Commit

Permalink
TSpace: simplifying usage of monotonic nanoTime()
Browse files Browse the repository at this point in the history
  • Loading branch information
barspi committed Nov 24, 2023
1 parent d9bc510 commit dcad985
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 74 deletions.
108 changes: 55 additions & 53 deletions jpos/src/main/java/org/jpos/space/TSpace.java
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;

Expand All @@ -40,8 +39,9 @@ public class TSpace<K,V> implements LocalSpace<K,V>, 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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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();
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand All @@ -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) { }
}
}
Expand Down Expand Up @@ -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().<br>
* 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
Expand All @@ -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;
}
}

Expand Down
43 changes: 22 additions & 21 deletions jpos/src/test/java/org/jpos/space/TSpaceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)) {
Expand All @@ -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)) {
Expand All @@ -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()");
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit dcad985

Please sign in to comment.