diff options
author | Jim Hahn <jrh3@att.com> | 2018-06-25 17:23:02 -0400 |
---|---|---|
committer | Jim Hahn <jrh3@att.com> | 2018-06-25 17:40:04 -0400 |
commit | d8a6909ef0bb3433265295107b1450122eac919a (patch) | |
tree | 8a625e85a2712c96fb10d53dae2cb33ac9c6ce65 /policy-core/src/main/java/org | |
parent | b5a47dcb16af9e90e46d08d0b5faedb3302bca8b (diff) |
Added expiration timer to default locking strategy
Fixed comment (missing "}").
Changed default age from 15 hours to 15 minutes.
Only increment test time by 1 when testing hashCode.
Change-Id: I70ef6cfd34e31ded45b3975f33e5b0ba22afa627
Issue-ID: POLICY-912
Signed-off-by: Jim Hahn <jrh3@att.com>
Diffstat (limited to 'policy-core/src/main/java/org')
-rw-r--r-- | policy-core/src/main/java/org/onap/policy/drools/core/lock/SimpleLockManager.java | 198 |
1 files changed, 190 insertions, 8 deletions
diff --git a/policy-core/src/main/java/org/onap/policy/drools/core/lock/SimpleLockManager.java b/policy-core/src/main/java/org/onap/policy/drools/core/lock/SimpleLockManager.java index c2d58b85..9bcf2598 100644 --- a/policy-core/src/main/java/org/onap/policy/drools/core/lock/SimpleLockManager.java +++ b/policy-core/src/main/java/org/onap/policy/drools/core/lock/SimpleLockManager.java @@ -23,8 +23,14 @@ package org.onap.policy.drools.core.lock; import static org.onap.policy.drools.core.lock.LockRequestFuture.MSG_NULL_OWNER; import static org.onap.policy.drools.core.lock.LockRequestFuture.MSG_NULL_RESOURCE_ID; import static org.onap.policy.drools.core.lock.LockRequestFuture.makeNullArgException; -import java.util.concurrent.ConcurrentHashMap; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import org.onap.policy.common.utils.time.CurrentTime; import org.onap.policy.drools.core.lock.PolicyResourceLockFeatureAPI.Callback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,9 +44,33 @@ public class SimpleLockManager { protected static Logger logger = LoggerFactory.getLogger(SimpleLockManager.class); /** - * Maps a resource to the owner that holds the lock on it. + * Maximum age, in milliseconds, before a lock is considered stale and released. */ - private ConcurrentHashMap<String, String> resource2owner = new ConcurrentHashMap<>(); + protected static final long MAX_AGE_MS = TimeUnit.MINUTES.toMillis(15L); + + /** + * Used to access the current time. May be overridden by junit tests. + */ + private static CurrentTime currentTime = new CurrentTime(); + + /** + * Used to synchronize updates to {@link #resource2data} and {@link #locks}. + */ + private final Object locker = new Object(); + + /** + * Maps a resource to its lock data. Lock data is stored in both this and in + * {@link #locks}. + */ + private final Map<String, Data> resource2data = new HashMap<>(); + + /** + * Lock data, sorted by expiration time. Lock data is stored in both this and in + * {@link #resource2data}. Whenever a lock operation is performed, this structure is + * examined and any expired locks are removed; thus no timer threads are needed to + * remove expired locks. + */ + private final SortedSet<Data> locks = new TreeSet<>(); /** * @@ -84,9 +114,20 @@ public class SimpleLockManager { throw makeNullArgException(MSG_NULL_OWNER); } - boolean locked = (resource2owner.putIfAbsent(resourceId, owner) == null); + Data existingLock; + + synchronized(locker) { + cleanUpLocks(); + + if((existingLock = resource2data.get(resourceId)) == null) { + Data data = new Data(owner, resourceId, currentTime.getMillis() + MAX_AGE_MS); + resource2data.put(resourceId, data); + locks.add(data); + } + } - if (!locked && owner.equals(resource2owner.get(resourceId))) { + boolean locked = (existingLock == null); + if (existingLock != null && owner.equals(existingLock.getOwner())) { throw new IllegalStateException("lock for resource " + resourceId + " already owned by " + owner); } @@ -112,8 +153,24 @@ public class SimpleLockManager { if (owner == null) { throw makeNullArgException(MSG_NULL_OWNER); } + + Data data; + + synchronized(locker) { + cleanUpLocks(); + + if((data = resource2data.get(resourceId)) != null) { + if(owner.equals(data.getOwner())) { + resource2data.remove(resourceId); + locks.remove(data); + + } else { + data = null; + } + } + } - boolean unlocked = resource2owner.remove(resourceId, owner); + boolean unlocked = (data != null); logger.info("unlock resource {} owner {} = {}", resourceId, owner, unlocked); return unlocked; @@ -132,7 +189,13 @@ public class SimpleLockManager { throw makeNullArgException(MSG_NULL_RESOURCE_ID); } - boolean locked = resource2owner.containsKey(resourceId); + boolean locked; + + synchronized(locker) { + cleanUpLocks(); + + locked = resource2data.containsKey(resourceId); + } logger.debug("resource {} isLocked = {}", resourceId, locked); @@ -157,9 +220,128 @@ public class SimpleLockManager { throw makeNullArgException(MSG_NULL_OWNER); } - boolean locked = owner.equals(resource2owner.get(resourceId)); + Data data; + + synchronized(locker) { + cleanUpLocks(); + + data = resource2data.get(resourceId); + } + + boolean locked = (data != null && owner.equals(data.getOwner())); logger.debug("resource {} isLockedBy {} = {}", resourceId, owner, locked); return locked; } + + /** + * Releases expired locks. + */ + private void cleanUpLocks() { + long tcur = currentTime.getMillis(); + + synchronized(locker) { + Iterator<Data> it = locks.iterator(); + while(it.hasNext()) { + Data d = it.next(); + if(d.getExpirationMs() <= tcur) { + it.remove(); + resource2data.remove(d.getResource()); + + } else { + break; + } + } + } + } + + /** + * Data for a single Lock. Sorts by expiration time, then resource, and + * then owner. + */ + protected static class Data implements Comparable<Data> { + + /** + * Owner of the lock. + */ + private final String owner; + + /** + * Resource that is locked. + */ + private final String resource; + + /** + * Time when the lock will expire, in milliseconds. + */ + private final long texpireMs; + + /** + * + * @param resource + * @param owner + * @param texpireMs + */ + public Data(String owner, String resource, long texpireMs) { + this.owner = owner; + this.resource = resource; + this.texpireMs = texpireMs; + } + + public String getOwner() { + return owner; + } + + public String getResource() { + return resource; + } + + public long getExpirationMs() { + return texpireMs; + } + + @Override + public int compareTo(Data o) { + int diff = Long.compare(texpireMs, o.texpireMs); + if(diff == 0) + diff = resource.compareTo(o.resource); + if(diff == 0) + diff = owner.compareTo(o.owner); + return diff; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((owner == null) ? 0 : owner.hashCode()); + result = prime * result + ((resource == null) ? 0 : resource.hashCode()); + result = prime * result + (int) (texpireMs ^ (texpireMs >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Data other = (Data) obj; + if (owner == null) { + if (other.owner != null) + return false; + } else if (!owner.equals(other.owner)) + return false; + if (resource == null) { + if (other.resource != null) + return false; + } else if (!resource.equals(other.resource)) + return false; + if (texpireMs != other.texpireMs) + return false; + return true; + } + } } |