From 17f3d1326732c06fafab6358e468187c2f14218b Mon Sep 17 00:00:00 2001 From: Myles Date: Thu, 30 Jun 2016 00:26:12 +0100 Subject: [PATCH] Fix #450, ProtocolSupport concurrency issues, also make listwrapper sync'd --- .../us/myles/ViaVersion/ViaVersionPlugin.java | 39 ++- .../myles/ViaVersion/util/ConcurrentList.java | 268 ++++++++++++++++++ .../us/myles/ViaVersion/util/ListWrapper.java | 108 ++++--- 3 files changed, 380 insertions(+), 35 deletions(-) create mode 100644 src/main/java/us/myles/ViaVersion/util/ConcurrentList.java diff --git a/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java b/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java index b49b24c07..0f1620687 100644 --- a/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java +++ b/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java @@ -27,6 +27,7 @@ import us.myles.ViaVersion.handlers.ViaVersionInitializer; import us.myles.ViaVersion.protocols.base.ProtocolInfo; import us.myles.ViaVersion.update.UpdateListener; import us.myles.ViaVersion.update.UpdateUtil; +import us.myles.ViaVersion.util.ConcurrentList; import us.myles.ViaVersion.util.ListWrapper; import us.myles.ViaVersion.util.ProtocolSupportUtil; import us.myles.ViaVersion.util.ReflectionUtil; @@ -53,6 +54,20 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI { @Getter private ViaConfig conf; + public ViaVersionPlugin() { + // Check if we're using protocol support too + protocolSupport = Bukkit.getPluginManager().getPlugin("ProtocolSupport") != null; + + if (protocolSupport) { + getLogger().info("Patching to prevent concurrency issues..."); + try { + patchLists(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + @Override public void onLoad() { // Config magic @@ -87,9 +102,6 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI { compatSpigotBuild = false; } - // Check if we're using protocol support too - protocolSupport = Bukkit.getPluginManager().getPlugin("ProtocolSupport") != null; - // Generate classes needed (only works if it's compat or ps) ClassGenerator.generate(); lateBind = !isBinded(); @@ -248,6 +260,27 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI { } } + + public void patchLists() throws Exception { + Object connection = getServerConnection(); + if (connection == null) { + getLogger().warning("We failed to find the ServerConnection? :( What server are you running?"); + return; + } + for (Field field : connection.getClass().getDeclaredFields()) { + field.setAccessible(true); + final Object value = field.get(connection); + if (value instanceof List) { + if (!(value instanceof ConcurrentList)) { + ConcurrentList list = new ConcurrentList(); + list.addAll((List) value); + field.set(connection, list); + } + } + } + } + + public boolean isBinded() { try { Object connection = getServerConnection(); diff --git a/src/main/java/us/myles/ViaVersion/util/ConcurrentList.java b/src/main/java/us/myles/ViaVersion/util/ConcurrentList.java new file mode 100644 index 000000000..39d3f8f2e --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/util/ConcurrentList.java @@ -0,0 +1,268 @@ +package us.myles.ViaVersion.util; + +import lombok.SneakyThrows; + +import java.lang.reflect.Field; +import java.util.*; + +/** + * Created by wea_ondara licensed under MIT + * Same license as in LICENSE + * + * Taken from: + * https://github.com/weaondara/BungeePerms/blob/master/src/main/java/net/alpenblock/bungeeperms/util/ConcurrentList.java + * @param List Type + */ +public class ConcurrentList extends ArrayList { + + private final Object lock = new Object(); + + @Override + public boolean add(E e) { + synchronized (lock) { + return super.add(e); + } + } + + @Override + public void add(int index, E element) { + synchronized (lock) { + super.add(index, element); + } + } + + @Override + public boolean addAll(Collection c) { + synchronized (lock) { + return super.addAll(c); + } + } + + @Override + public boolean addAll(int index, Collection c) { + synchronized (lock) { + return super.addAll(index, c); + } + } + + @Override + public void clear() { + synchronized (lock) { + super.clear(); + } + } + + @Override + @SneakyThrows + public Object clone() { + synchronized (lock) { + ConcurrentList clist = (ConcurrentList) super.clone(); + clist.modCount = 0; + Field f = ArrayList.class.getDeclaredField("elementData"); + f.setAccessible(true); + f.set(clist, Arrays.copyOf((Object[]) f.get(this), this.size())); + + return clist; + } + } + + @Override + public boolean contains(Object o) { + synchronized (lock) { + return super.contains(o); + } + } + + @Override + public void ensureCapacity(int minCapacity) { + synchronized (lock) { + super.ensureCapacity(minCapacity); + } + } + + @Override + public E get(int index) { + synchronized (lock) { + return super.get(index); + } + } + + @Override + public int indexOf(Object o) { + synchronized (lock) { + return super.indexOf(o); + } + } + + @Override + public int lastIndexOf(Object o) { + synchronized (lock) { + return super.lastIndexOf(o); + } + } + + @Override + public E remove(int index) { + synchronized (lock) { + return super.remove(index); + } + } + + @Override + public boolean remove(Object o) { + synchronized (lock) { + return super.remove(o); + } + } + + @Override + public boolean removeAll(Collection c) { + synchronized (lock) { + return super.removeAll(c); + } + } + + @Override + public boolean retainAll(Collection c) { + synchronized (lock) { + return super.retainAll(c); + } + } + + @Override + public E set(int index, E element) { + synchronized (lock) { + return super.set(index, element); + } + } + + @Override + public List subList(int fromIndex, int toIndex) { + synchronized (lock) { + return super.subList(fromIndex, toIndex); + } + } + + @Override + public Object[] toArray() { + synchronized (lock) { + return super.toArray(); + } + } + + @Override + public T[] toArray(T[] a) { + synchronized (lock) { + return super.toArray(a); + } + } + + @Override + public void trimToSize() { + synchronized (lock) { + super.trimToSize(); + } + } + + @Override + public ListIterator listIterator() { + return new ListItr(0); + } + + @Override + public Iterator iterator() { + return new Itr(); + } + + private class Itr implements Iterator { + + protected int cursor; + protected int lastRet; + final ConcurrentList l; + + public Itr() { + cursor = 0; + lastRet = -1; + l = (ConcurrentList) ConcurrentList.this.clone(); + } + + @Override + public boolean hasNext() { + return cursor < l.size(); + } + + @Override + public E next() { + int i = cursor; + if (i >= l.size()) { + throw new NoSuchElementException(); + } + cursor = i + 1; + return (E) l.get(lastRet = i); + } + + @Override + public void remove() { + if (lastRet < 0) { + throw new IllegalStateException(); + } + + l.remove(lastRet); + ConcurrentList.this.remove(lastRet); + cursor = lastRet; + lastRet = -1; + } + } + + public class ListItr extends Itr implements ListIterator { + + ListItr(int index) { + super(); + cursor = index; + } + + @Override + public boolean hasPrevious() { + return cursor > 0; + } + + @Override + public int nextIndex() { + return cursor; + } + + @Override + public int previousIndex() { + return cursor - 1; + } + + @Override + public E previous() { + int i = cursor - 1; + if (i < 0) { + throw new NoSuchElementException(); + } + cursor = i; + return (E) l.get(lastRet = i); + } + + @Override + public void set(E e) { + if (lastRet < 0) { + throw new IllegalStateException(); + } + + l.set(lastRet, e); + ConcurrentList.this.set(lastRet, e); + } + + @Override + public void add(E e) { + int i = cursor; + l.add(i, e); + ConcurrentList.this.add(i, e); + cursor = i + 1; + lastRet = -1; + } + } +} \ No newline at end of file diff --git a/src/main/java/us/myles/ViaVersion/util/ListWrapper.java b/src/main/java/us/myles/ViaVersion/util/ListWrapper.java index 9127b30a2..9e782e950 100644 --- a/src/main/java/us/myles/ViaVersion/util/ListWrapper.java +++ b/src/main/java/us/myles/ViaVersion/util/ListWrapper.java @@ -1,11 +1,9 @@ package us.myles.ViaVersion.util; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; +import java.util.*; public abstract class ListWrapper implements List { + public final Object lock = new Object(); private final List list; public ListWrapper(List inputList) { @@ -20,123 +18,169 @@ public abstract class ListWrapper implements List { @Override public synchronized int size() { - return this.list.size(); + synchronized (lock) { + return this.list.size(); + } } @Override public synchronized boolean isEmpty() { - return this.list.isEmpty(); + synchronized (lock) { + return this.list.isEmpty(); + } } @Override public synchronized boolean contains(Object o) { - return this.list.contains(o); + synchronized (lock) { + return this.list.contains(o); + } } @Override public synchronized Iterator iterator() { - return this.list.iterator(); + synchronized (lock) { + return listIterator(); + } } @Override public synchronized Object[] toArray() { - return this.list.toArray(); + synchronized (lock) { + return this.list.toArray(); + } } @Override public synchronized boolean add(Object o) { - handleAdd(o); - return this.list.add(o); + synchronized (lock) { + handleAdd(o); + return this.list.add(o); + } } @Override public synchronized boolean remove(Object o) { - return this.list.remove(o); + synchronized (lock) { + return this.list.remove(o); + } } @Override public synchronized boolean addAll(Collection c) { - for (Object o : c) { - handleAdd(o); + synchronized (lock) { + for (Object o : c) { + handleAdd(o); + } + return this.list.addAll(c); } - return this.list.addAll(c); } @Override public synchronized boolean addAll(int index, Collection c) { - for (Object o : c) { - handleAdd(o); + synchronized (lock) { + for (Object o : c) { + handleAdd(o); + } + return this.list.addAll(index, c); } - return this.list.addAll(index, c); } @Override public synchronized void clear() { - this.list.clear(); + synchronized (lock) { + this.list.clear(); + } } @Override public synchronized Object get(int index) { - return this.list.get(index); + synchronized (lock) { + return this.list.get(index); + } } @Override public synchronized Object set(int index, Object element) { - return this.list.set(index, element); + synchronized (lock) { + return this.list.set(index, element); + } } @Override public synchronized void add(int index, Object element) { - this.list.add(index, element); + synchronized (lock) { + this.list.add(index, element); + } } @Override public synchronized Object remove(int index) { - return this.list.remove(index); + synchronized (lock) { + return this.list.remove(index); + } } @Override public synchronized int indexOf(Object o) { - return this.list.indexOf(o); + synchronized (lock) { + return this.list.indexOf(o); + } } @Override public synchronized int lastIndexOf(Object o) { - return this.list.lastIndexOf(o); + synchronized (lock) { + return this.list.lastIndexOf(o); + } } @Override public synchronized ListIterator listIterator() { - return this.list.listIterator(); + synchronized (lock) { + return this.list.listIterator(); + } } @Override public synchronized ListIterator listIterator(int index) { - return this.list.listIterator(index); + synchronized (lock) { + return this.list.listIterator(index); + } } @Override public synchronized List subList(int fromIndex, int toIndex) { - return this.list.subList(fromIndex, toIndex); + synchronized (lock) { + return this.list.subList(fromIndex, toIndex); + } } @Override public synchronized boolean retainAll(Collection c) { - return this.list.retainAll(c); + synchronized (lock) { + return this.list.retainAll(c); + } } @Override public synchronized boolean removeAll(Collection c) { - return this.list.removeAll(c); + synchronized (lock) { + return this.list.removeAll(c); + } } @Override public synchronized boolean containsAll(Collection c) { - return this.list.containsAll(c); + synchronized (lock) { + return this.list.containsAll(c); + } } @Override public synchronized Object[] toArray(Object[] a) { - return this.list.toArray(a); + synchronized (lock) { + return this.list.toArray(a); + } } }