Maybe fix blocking issue with #getPermissions

This commit is contained in:
Luck 2016-11-01 19:38:08 +00:00
parent 42882ebe11
commit ebab79c4ca
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
2 changed files with 125 additions and 87 deletions

View File

@ -46,7 +46,6 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -72,10 +71,85 @@ public abstract class PermissionHolder {
private final Set<Node> nodes = new HashSet<>(); private final Set<Node> nodes = new HashSet<>();
private final Set<Node> transientNodes = new HashSet<>(); private final Set<Node> transientNodes = new HashSet<>();
private Cache<ImmutableSortedSet<LocalizedNode>> cache = new Cache<>(); private Cache<ImmutableSet<Node>> enduringCache = new Cache<>(() -> {
private Cache<ImmutableSortedSet<LocalizedNode>> mergedCache = new Cache<>(); synchronized (nodes) {
private Cache<ImmutableSet<Node>> enduringCache = new Cache<>(); return ImmutableSet.copyOf(nodes);
private Cache<ImmutableSet<Node>> transientCache = new Cache<>(); }
});
private Cache<ImmutableSet<Node>> transientCache = new Cache<>(() -> {
synchronized (transientNodes) {
return ImmutableSet.copyOf(transientNodes);
}
});
private Cache<ImmutableSortedSet<LocalizedNode>> cache = new Cache<>(() -> {
TreeSet<LocalizedNode> combined = new TreeSet<>(PriorityComparator.reverse());
Set<Node> enduring = getNodes();
if (!enduring.isEmpty()) {
combined.addAll(getNodes().stream()
.map(n -> makeLocal(n, getObjectName()))
.collect(Collectors.toList())
);
}
Set<Node> tran = getTransientNodes();
if (!tran.isEmpty()) {
combined.addAll(getTransientNodes().stream()
.map(n -> makeLocal(n, getObjectName()))
.collect(Collectors.toList())
);
}
Iterator<LocalizedNode> it = combined.iterator();
Set<LocalizedNode> higherPriority = new HashSet<>();
iterate:
while (it.hasNext()) {
LocalizedNode entry = it.next();
for (LocalizedNode h : higherPriority) {
if (entry.getNode().almostEquals(h.getNode())) {
it.remove();
continue iterate;
}
}
higherPriority.add(entry);
}
return ImmutableSortedSet.copyOfSorted(combined);
});
private Cache<ImmutableSortedSet<LocalizedNode>> mergedCache = new Cache<>(() -> {
TreeSet<LocalizedNode> combined = new TreeSet<>(PriorityComparator.reverse());
Set<Node> enduring = getNodes();
if (!enduring.isEmpty()) {
combined.addAll(getNodes().stream()
.map(n -> makeLocal(n, getObjectName()))
.collect(Collectors.toList())
);
}
Set<Node> tran = getTransientNodes();
if (!tran.isEmpty()) {
combined.addAll(getTransientNodes().stream()
.map(n -> makeLocal(n, getObjectName()))
.collect(Collectors.toList())
);
}
Iterator<LocalizedNode> it = combined.iterator();
Set<LocalizedNode> higherPriority = new HashSet<>();
iterate:
while (it.hasNext()) {
LocalizedNode entry = it.next();
for (LocalizedNode h : higherPriority) {
if (entry.getNode().equalsIgnoringValueOrTemp(h.getNode())) {
it.remove();
continue iterate;
}
}
higherPriority.add(entry);
}
return ImmutableSortedSet.copyOfSorted(combined);
});
@Getter @Getter
private final Lock ioLock = new ReentrantLock(); private final Lock ioLock = new ReentrantLock();
@ -83,25 +157,11 @@ public abstract class PermissionHolder {
public abstract String getFriendlyName(); public abstract String getFriendlyName();
public Set<Node> getNodes() { public Set<Node> getNodes() {
Optional<ImmutableSet<Node>> opt = enduringCache.getIfPresent(); return enduringCache.get();
if (opt.isPresent()) {
return opt.get();
}
synchronized (nodes) {
return enduringCache.get(() -> ImmutableSet.copyOf(nodes));
}
} }
public Set<Node> getTransientNodes() { public Set<Node> getTransientNodes() {
Optional<ImmutableSet<Node>> opt = transientCache.getIfPresent(); return transientCache.get();
if (opt.isPresent()) {
return opt.get();
}
synchronized (transientNodes) {
return transientCache.get(() -> ImmutableSet.copyOf(transientNodes));
}
} }
private void invalidateCache(boolean enduring) { private void invalidateCache(boolean enduring) {
@ -119,61 +179,7 @@ public abstract class PermissionHolder {
* @return the holders transient and permanent nodes * @return the holders transient and permanent nodes
*/ */
public SortedSet<LocalizedNode> getPermissions(boolean mergeTemp) { public SortedSet<LocalizedNode> getPermissions(boolean mergeTemp) {
Optional<ImmutableSortedSet<LocalizedNode>> opt = mergeTemp ? mergedCache.getIfPresent() : cache.getIfPresent(); return mergeTemp ? mergedCache.get() : cache.get();
if (opt.isPresent()) {
return opt.get();
}
Supplier<ImmutableSortedSet<LocalizedNode>> supplier = () -> {
// Create sorted set
TreeSet<LocalizedNode> combined = new TreeSet<>(PriorityComparator.reverse());
// Flatten enduring and transient nodes
Set<Node> enduring = getNodes();
if (!enduring.isEmpty()) {
combined.addAll(getNodes().stream()
.map(n -> makeLocal(n, getObjectName()))
.collect(Collectors.toList())
);
}
Set<Node> tran = getTransientNodes();
if (!tran.isEmpty()) {
combined.addAll(getTransientNodes().stream()
.map(n -> makeLocal(n, getObjectName()))
.collect(Collectors.toList())
);
}
// Create an iterator over all permissions being considered
Iterator<LocalizedNode> it = combined.iterator();
// Temporary set to store high priority values
Set<LocalizedNode> higherPriority = new HashSet<>();
// Iterate through each node being considered
iterate:
while (it.hasNext()) {
LocalizedNode entry = it.next();
// Check through all of the higher priority nodes
for (LocalizedNode h : higherPriority) {
// Check to see if the entry being considered was already processed at a higher priority
if (mergeTemp ? entry.getNode().equalsIgnoringValueOrTemp(h.getNode()) : entry.getNode().almostEquals(h.getNode())) {
it.remove();
continue iterate;
}
}
// This entry will be kept.
higherPriority.add(entry);
}
return ImmutableSortedSet.copyOfSorted(combined);
};
return mergeTemp ? mergedCache.get(supplier) : cache.get(supplier);
} }
/** /**
@ -239,7 +245,7 @@ public abstract class PermissionHolder {
excludedGroups.add(getObjectName().toLowerCase()); excludedGroups.add(getObjectName().toLowerCase());
Set<Node> parents = getPermissions(true).stream() Set<Node> parents = all.stream()
.map(LocalizedNode::getNode) .map(LocalizedNode::getNode)
.filter(Node::getValue) .filter(Node::getValue)
.filter(Node::isGroupNode) .filter(Node::isGroupNode)

View File

@ -22,30 +22,62 @@
package me.lucko.luckperms.common.utils; package me.lucko.luckperms.common.utils;
import lombok.RequiredArgsConstructor;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier; import java.util.function.Supplier;
/**
* Thread-safe caching utility
* @param <T> the type being stored
*/
@RequiredArgsConstructor
public class Cache<T> { public class Cache<T> {
private T t = null; private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Supplier<T> supplier;
public T get(Supplier<T> supplier) { private T cached = null;
synchronized (this) {
if (t == null) { public T get() {
t = supplier.get(); lock.readLock().lock();
try {
if (cached != null) {
return cached;
} }
return t; } finally {
lock.readLock().unlock();
}
lock.writeLock().lock();
try {
// Check again
if (cached != null) {
return cached;
}
cached = supplier.get();
return cached;
} finally {
lock.writeLock().unlock();
} }
} }
public Optional<T> getIfPresent() { public Optional<T> getIfPresent() {
synchronized (this) { lock.readLock().lock();
return Optional.ofNullable(t); try {
return Optional.ofNullable(cached);
} finally {
lock.readLock().unlock();
} }
} }
public void invalidate() { public void invalidate() {
synchronized (this) { lock.writeLock().lock();
t = null; try {
cached = null;
} finally {
lock.writeLock().unlock();
} }
} }
} }