Tiquality

Tiquality

82.2k Downloads

Crash of the server when a specific player logs on

jinkhya opened this issue ยท 3 comments

commented

Hello,

Everytime a player logs on we're getting this crash :
https://hastebin.com/akiziruwoz.coffeescript

If you need any kind of information, let me know.

commented

If you still have the log itself, i'd like to get my hands on that so I know in what context to look.

commented
commented

I am unable to reproduce this issue, and looking at the code this should not theoretically be possible because ReentrantLock should prevent this. Adding help-wanted label, I need new eyes to figure this out.

java.util.ConcurrentModificationException: null
	at java.util.TreeMap$PrivateEntryIterator.nextEntry(TreeMap.java:1211) ~[?:1.8.0_191]
	at java.util.TreeMap$ValueIterator.next(TreeMap.java:1256) ~[?:1.8.0_191]
	at cf.terminator.tiquality.tracking.TrackerManager.tickUntil(TrackerManager.java:69) ~[TrackerManager.class:?]
	at cf.terminator.tiquality.monitor.TickMaster.onServerTick(TickMaster.java:64) ~[TickMaster.class:?]
	at net.minecraftforge.fml.common.eventhandler.ASMEventHandler_2625_TickMaster_onServerTick_ServerTickEvent.invoke(.dynamic) ~[?:?]
	at net.minecraftforge.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:90) ~[ASMEventHandler.class:?]
	at net.minecraftforge.fml.common.eventhandler.EventBus.post(EventBus.java:746) ~[EventBus.class:?]
	at net.minecraftforge.fml.common.eventhandler.EventBus.post(EventBus.java:696) ~[EventBus.class:?]
	at net.minecraftforge.fml.common.FMLCommonHandler.onPostServerTick(FMLCommonHandler.java:266) ~[FMLCommonHandler.class:?]
	at net.minecraft.server.MinecraftServer.func_71217_p(MinecraftServer.java:712) ~[MinecraftServer.class:?]
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:526) [MinecraftServer.class:?]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_191]

If someone can shed some light on this, that would be nice, the snippet below should be all you need to assess the problem. All functions except TickUntil can be called from any thread.

package cf.terminator.tiquality.tracking;

import cf.terminator.tiquality.interfaces.TiqualityWorld;
import cf.terminator.tiquality.interfaces.Tracker;
import net.minecraft.nbt.NBTTagCompound;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.TreeMap;
import java.util.concurrent.locks.ReentrantLock;

@SuppressWarnings("WeakerAccess")
public class TrackerManager {

    /**
     * Holds a list of all registered trackers
     * See: cf.terminator.tiquality.api.Tracking#registerCustomTracker(java.lang.Class)
     */
    public static final HashMap<String, Class<? extends Tracker>> REGISTERED_TRACKER_TYPES = new HashMap<>();

    /**
     * Variable holding all TrackerHolders, and thus: trackers.
     */
    private static final TreeMap<Long, TrackerHolder> TRACKER_LIST = new TreeMap<>();
    private static final ReentrantLock TRACKER_LIST_LOCK = new ReentrantLock();
    /**
     * Loop over the protected set.
     */
    public static <T> T foreach(Action<T> foreach){
        TRACKER_LIST_LOCK.lock();
        try {
            for (TrackerHolder holder : TRACKER_LIST.values()) {
                foreach.each(holder.getTracker());
                if (foreach.stop) {
                    return foreach.value;
                }
            }
            return foreach.value;
        }finally {
            TRACKER_LIST_LOCK.unlock();
        }
    }

    /**
     * Gets a tracker by ID, but ONLY if it's currently loaded.
     */
    @Nullable
    public static Tracker getTrackerByID(long id){
        TRACKER_LIST_LOCK.lock();
        try{
            TrackerHolder holder = TRACKER_LIST.get(id);
            return holder == null ? null : holder.getTracker();
        }finally {
            TRACKER_LIST_LOCK.unlock();
        }
    }

    /**
     * Ticks all scheduled objects until either time runs out or all objects have been ticked.
     * THIS MAY ONLY BE CALLED ON THE MAIN THREAD!
     * @param time the time (System.nanoTime()) when the ticking should stop.
     */
    public static void tickUntil(long time){
        TRACKER_LIST_LOCK.lock();
        boolean hasWork = true;
        while(System.nanoTime() < time && hasWork) {
            hasWork = false;
            for(TrackerHolder holder : TRACKER_LIST.values()){
                Tracker tracker = holder.getTracker();
                if(tracker.needsTick()){
                    hasWork = true;
                    tracker.grantTick();
                }
            }
        }
        TRACKER_LIST_LOCK.unlock();
    }

    /**
     * Removes trackers which do not tick anymore due to their tickables being unloaded
     */
    public static void removeInactiveTrackers(){
        TRACKER_LIST_LOCK.lock();
        TRACKER_LIST.entrySet().removeIf(entry -> {
            Tracker tracker = entry.getValue().getTracker();
            if(tracker.shouldUnload()){
                tracker.onUnload();
                return true;
            }else{
                return false;
            }
        });
        TRACKER_LIST_LOCK.unlock();
    }

    /**
     * Instantiates a new tracker using an NBT compound tag.
     * If the tracker already exists, a reference to the pre-existing tracker is given.
     * @param tagCompound The NBT tag compound
     * @return the tracker
     */
    @Nullable
    public static TrackerHolder readHolder(TiqualityWorld world, NBTTagCompound tagCompound){
        String type = tagCompound.getString("type");
        if(type.equals("")){
            return null;
        }
        long id = tagCompound.getLong("id");
        TRACKER_LIST_LOCK.lock();
        try {
            TrackerHolder holder = TRACKER_LIST.get(id);
            if (holder != null) {
                return holder;
            }
            holder = TrackerHolder.readHolder(world, tagCompound);
            if (holder == null) {
                return null;
            }
            return holder;
        }finally {
            TRACKER_LIST_LOCK.unlock();
        }
    }

    /**
     * Creates a new tracker, and saves it to disk.
     * @param tracker The tracker, it must be a newly created tracker!
     * @param <T> The tracker.
     * @throws IllegalStateException if the tracker already has a holder assigned, indicative of a programming error.
     * @return the tracker holder
     */
    @Nonnull
    public static <T extends Tracker> TrackerHolder<T> createNewTrackerHolder(TiqualityWorld world, T tracker){
        TrackerHolder<T> holder = tracker.getHolder();
        if(holder != null){
            throw new IllegalStateException("This tracker wants to be saved as if it was new, but it's not! : " + tracker.toString());
        }
        return TrackerHolder.createNewTrackerHolder(world, tracker);
    }

    public static void addTrackerHolder(TrackerHolder holder){
        TRACKER_LIST_LOCK.lock();
        try{
            if(TRACKER_LIST.put(holder.getId(), holder) != null){
                throw new IllegalStateException("Attempted to save two different trackerholder instances!");
            }
        }finally {
            TRACKER_LIST_LOCK.unlock();
        }
    }

    public static abstract class Action<T>{
        public T value = null;
        private boolean stop = false;

        public void stop(T value){
            this.stop = true;
            this.value = value;
        }
        public abstract void each(Tracker tracker);
    }
}