001/* 002 * Trident - A Multithreaded Server Alternative 003 * Copyright 2014 The TridentSDK Team 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package net.tridentsdk.server.concurrent; 018 019import net.tridentsdk.server.TridentServer; 020import net.tridentsdk.util.TridentLogger; 021 022import javax.annotation.concurrent.GuardedBy; 023import java.util.LinkedList; 024import java.util.Queue; 025import java.util.concurrent.ConcurrentLinkedQueue; 026import java.util.concurrent.CountDownLatch; 027import java.util.concurrent.TimeUnit; 028import java.util.concurrent.atomic.LongAdder; 029import java.util.concurrent.locks.LockSupport; 030 031/** 032 * Synchronizes plugin calls with the ticking thread 033 * 034 * <p>This class is necessary because plugins that are not following the Trident threading model can mutate and break 035 * the state consistency as described by the model. This is not good because plugins should not be expected to follow 036 * the threading model.</p> 037 * 038 * <p>A demonstration of the state consistency being broken by conflicting thread-models: 039 * <pre>{@code 040 * WeatherConditions cnd = world.weather(); 041 * if (!cnd.isRaining()) { // If it is not raining 042 * cnd.setRaining(true); // start raining 043 * } 044 * 045 * // Should return true! 046 * boolean stillRaining = cnd.isRaining(); 047 * }</pre> 048 * The {@code stillRaining} variable can return false: 049 * <pre> 050 * World Tick Thread ---> raining == false -----------------> raining = !true -> raining == false 051 * Plugin Thread -----> raining == false -> raining = true -----------------------> stillRaining == false 052 * </pre> 053 * Not only do we not provide a set raining method, this class will be used to hold off plugin notification of server 054 * events until the entire tick has been processed. 055 * </p> 056 * 057 * <p>Unless you want to severely mess up the server, do not call any methods in this class unless: 058 * <ul> 059 * <li>You know what you are doing (unlikely)</li> 060 * <li>Developing Trident (in which case you need to check with Pierre to ensure you're doing it right)</li> 061 * </ul></p> 062 * 063 * @author The TridentSDK Team 064 */ 065public final class TickSync { 066 private TickSync() { 067 } 068 069 public static volatile boolean DEBUG = false; 070 071 private static final Queue<String> expect = new ConcurrentLinkedQueue<>(); 072 private static final Queue<String> completed = new ConcurrentLinkedQueue<>(); 073 private static final LongAdder expected = new LongAdder(); 074 private static final LongAdder complete = new LongAdder(); 075 076 private static volatile CountDownLatch latch = new CountDownLatch(1); 077 078 @GuardedBy("this") 079 private static final Queue<Runnable> pluginTasks = new LinkedList<>(); 080 081 /** 082 * Increments the expected updates counter 083 */ 084 public static void increment(String s) { 085 expected.increment(); 086 if (DEBUG) { 087 expect.add(s + " T: " + Thread.currentThread().getName()); 088 } 089 } 090 091 /** 092 * Records that an update has occurred 093 * <p> 094 * <p>Signals the main thread to sync method to continue if the expected and update counters match</p> 095 */ 096 public static void complete(String s) { 097 complete.increment(); 098 if (DEBUG) { 099 completed.add(s + " T: " + Thread.currentThread().getName()); 100 } 101 102 if (canProceed()) { 103 latch.countDown(); 104 } 105 } 106 107 /** 108 * Tests to see if the expected and update counters match 109 * 110 * @return {@code true} to indicate that the ticking can proceed 111 */ 112 public static boolean canProceed() { 113 return expected.sum() == complete.sum(); 114 } 115 116 /** 117 * Blocks the thread until this method is called again by a {@link #complete(String)} method 118 */ 119 // TODO HOT CONCURRENT METHOD NOT INLINEABLE: TOO LARGE 120 // 215 bytes 121 public static void awaitSync() { 122 boolean b = canProceed(); 123 if (b) return; 124 125 try { 126 if (!latch.await(200, TimeUnit.MILLISECONDS)) { 127 TridentLogger.get().warn("Lost tick sync: complete-" + complete.sum() + " needed-" + expected.sum() + " proceed-" + b); 128 if (DEBUG) { 129 TridentLogger.get().warn(""); 130 TridentLogger.get().warn("===== PRINTING COMPLETED TASKS ====="); 131 completed.forEach(s -> TridentLogger.get().warn(s)); 132 TridentLogger.get().warn("===== END COMPLETED TASKS ====="); 133 TridentLogger.get().warn(""); 134 TridentLogger.get().warn("===== PRINTING NEEDED TASKS ====="); 135 expect.forEach(s -> TridentLogger.get().warn(s)); 136 TridentLogger.get().warn("===== END NEEDED TASKS ====="); 137 TridentLogger.get().warn("AVG TICK TIME: " + TridentServer.instance().mainThread().getAverageTickLength() + " ms"); 138 } else { 139 TridentLogger.get().warn("Enable debug to see extra information"); 140 } 141 } 142 } catch (InterruptedException e) { 143 e.printStackTrace(); 144 } 145 } 146 147 /** 148 * Resets the counters and the blocking mechanisms for the next tick iteration 149 */ 150 // TODO HOT CONCURRENT METHOD NOT INLINEABLE: TOO LARGE 151 // 40 bytes 152 public static void reset() { 153 expected.reset(); 154 complete.reset(); 155 expect.clear(); 156 completed.clear(); 157 latch = new CountDownLatch(1); 158 } 159 160 /** 161 * Synchronizes the task for later execution once the tick completes 162 * 163 * @param pluginTask the task 164 */ 165 // TODO HOT CONCURRENT METHOD NOT INLINEABLE: MAIN THREAD METHOD TOO LARGE 166 // ?? bytes 167 public static void sync(Runnable pluginTask) { 168 synchronized (TickSync.class) { 169 pluginTasks.add(pluginTask); 170 } 171 172 if (canProceed()) { 173 LockSupport.unpark(TridentServer.instance().mainThread()); 174 } 175 } 176 177 /** 178 * Waits for a task to become available, or blocks until waitNanos has elapsed, in which case {@code null} 179 * will be returned 180 * 181 * @param waitNanos the nanos to wait for a task 182 * @return a task, or {@code null} if there were none 183 * @throws InterruptedException if the current thread was interrupted in waiting for a task 184 */ 185 // TODO HOT CONCURRENT METHOD NOT INLINEABLE: TOO LARGE 186 // 48 bytes 187 public static Runnable waitForTask(long waitNanos) throws InterruptedException { 188 synchronized (TickSync.class) { 189 Runnable task; 190 if ((task = pluginTasks.poll()) == null) { 191 LockSupport.parkNanos(waitNanos); 192 task = pluginTasks.poll(); 193 } 194 195 return task; 196 } 197 } 198 199 /** 200 * Obtains the next task in the queue 201 * 202 * @return the next task, or {@code null} if there were none 203 */ 204 public static Runnable next() { 205 synchronized (TickSync.class) { 206 return pluginTasks.poll(); 207 } 208 } 209 210 /** 211 * Obtains the tasks left in the queue 212 * 213 * @return the amount of tasks left 214 */ 215 public static int left() { 216 synchronized (TickSync.class) { 217 return pluginTasks.size(); 218 } 219 } 220}