Remove thread tracker system

This commit is contained in:
P0nk
2022-08-11 14:57:26 +02:00
parent 4fb632ecdb
commit 618f312b84
26 changed files with 5 additions and 1396 deletions

View File

@@ -4,8 +4,6 @@ import java.util.HashMap;
import java.util.Map;
public class ServerConfig {
//Thread Tracker Configuration
public boolean USE_THREAD_TRACKER;
//Database Configuration
public String DB_URL_FORMAT;

View File

@@ -37,7 +37,6 @@ import constants.net.OpcodeConstants;
import constants.net.ServerConstants;
import net.netty.LoginServer;
import net.packet.Packet;
import net.server.audit.ThreadTracker;
import net.server.channel.Channel;
import net.server.coordinator.session.IpAddresses;
import net.server.coordinator.session.SessionCoordinator;
@@ -869,10 +868,6 @@ public class Server {
ThreadManager.getInstance().start();
initializeTimelyTasks(); // aggregated method for timely tasks thanks to lxconan
if (YamlConfig.config.server.USE_THREAD_TRACKER) {
ThreadTracker.getInstance().registerThreadTrackerTask();
}
try {
int worldCount = Math.min(GameConstants.WORLD_NAMES.length, YamlConfig.config.server.WORLDS);
@@ -945,7 +940,6 @@ public class Server {
long timeLeft = getTimeLeftForNextHour();
tMan.register(new CharacterDiseaseTask(), YamlConfig.config.server.UPDATE_INTERVAL, YamlConfig.config.server.UPDATE_INTERVAL);
tMan.register(new ReleaseLockTask(), MINUTES.toMillis(2), MINUTES.toMillis(2));
tMan.register(new CouponTask(), YamlConfig.config.server.COUPON_INTERVAL, timeLeft);
tMan.register(new RankingCommandTask(), MINUTES.toMillis(5), MINUTES.toMillis(5));
tMan.register(new RankingLoginTask(), YamlConfig.config.server.RANKING_INTERVAL, timeLeft);
@@ -1908,10 +1902,6 @@ public class Server {
List<Channel> allChannels = getAllChannels();
if (YamlConfig.config.server.USE_THREAD_TRACKER) {
ThreadTracker.getInstance().cancelThreadTrackerTask();
}
for (Channel ch : allChannels) {
while (!ch.finishedShutdown()) {
try {

View File

@@ -1,74 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.audit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Ronan
*/
public class LockCollector {
private static final LockCollector instance = new LockCollector();
public static LockCollector getInstance() {
return instance;
}
private final Map<Runnable, Integer> disposableLocks = new HashMap<>(200);
private final Lock lock = new ReentrantLock(true);
public void registerDisposeAction(Runnable r) {
lock.lock();
try {
disposableLocks.put(r, 0);
} finally {
lock.unlock();
}
}
public void runLockCollector() {
List<Runnable> toDispose = new ArrayList<>();
lock.lock();
try {
for (Entry<Runnable, Integer> e : disposableLocks.entrySet()) {
Integer eVal = e.getValue();
if (eVal > 5) { // updates each 2min
toDispose.add(e.getKey());
} else {
disposableLocks.put(e.getKey(), ++eVal);
}
}
} finally {
lock.unlock();
}
for (Runnable r : toDispose) {
r.run();
}
}
}

View File

@@ -1,279 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.audit;
import net.server.audit.locks.MonitoredLockType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import server.TimerManager;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author RonanLana
* <p>
* This tool has the main purpose of auditing deadlocks throughout the server and must be used only for debugging. The flag is USE_THREAD_TRACKER.
*/
public class ThreadTracker {
private static final Logger log = LoggerFactory.getLogger(ThreadTracker.class);
private static ThreadTracker instance = null;
public static ThreadTracker getInstance() {
if (instance == null) {
instance = new ThreadTracker();
}
return instance;
}
private final Lock ttLock = new ReentrantLock(true);
private final Map<Long, List<MonitoredLockType>> threadTracker = new HashMap<>();
private final Map<Long, Integer> threadUpdate = new HashMap<>();
private final Map<Long, Thread> threads = new HashMap<>();
private final Map<Long, AtomicInteger> lockCount = new HashMap<>();
private final Map<Long, MonitoredLockType> lockIds = new HashMap<>();
private final Map<Long, Long> lockThreads = new HashMap<>();
private final Map<Long, Integer> lockUpdate = new HashMap<>();
private final Map<MonitoredLockType, Map<Long, Integer>> locks = new HashMap<>();
ScheduledFuture<?> threadTrackerSchedule;
private String printThreadTrackerState(String dateFormat) {
Map<MonitoredLockType, List<Integer>> lockValues = new HashMap<>();
Set<Long> executingThreads = new HashSet<>();
for (Map.Entry<Long, AtomicInteger> lc : lockCount.entrySet()) {
if (lc.getValue().get() != 0) {
executingThreads.add(lockThreads.get(lc.getKey()));
MonitoredLockType lockId = lockIds.get(lc.getKey());
List<Integer> list = lockValues.get(lockId);
if (list == null) {
list = new ArrayList<>();
lockValues.put(lockId, list);
}
list.add(lc.getValue().get());
}
}
String s = "----------------------------\r\n" + dateFormat + "\r\n ";
s += "Lock-thread usage count:";
for (Map.Entry<MonitoredLockType, List<Integer>> lock : lockValues.entrySet()) {
s += ("\r\n " + lock.getKey().name() + ": ");
for (Integer i : lock.getValue()) {
s += (i + " ");
}
}
s += "\r\n\r\nThread opened lock path:";
for (Long tid : executingThreads) {
s += "\r\n";
for (MonitoredLockType lockid : threadTracker.get(tid)) {
s += (lockid.name() + " ");
}
s += "|";
}
s += "\r\n\r\n";
return s;
}
private static String printThreadLog(List<MonitoredLockType> stillLockedPath, String dateFormat) {
String s = "----------------------------\r\n" + dateFormat + "\r\n ";
for (MonitoredLockType lock : stillLockedPath) {
s += (lock.name() + " ");
}
s += "\r\n\r\n";
return s;
}
private static String printThreadStack(StackTraceElement[] list, String dateFormat) {
String s = "----------------------------\r\n" + dateFormat + "\r\n";
for (StackTraceElement stackTraceElement : list) {
s += (" " + stackTraceElement.toString() + "\r\n");
}
return s;
}
public void accessThreadTracker(boolean update, boolean lock, MonitoredLockType lockId, long lockOid) {
ttLock.lock();
try {
if (update) {
if (!lock) { // update tracker
List<Long> toRemove = new ArrayList<>();
for (Long l : threadUpdate.keySet()) {
int next = threadUpdate.get(l) + 1;
if (next == 4) {
List<MonitoredLockType> tt = threadTracker.get(l);
if (tt.isEmpty()) {
toRemove.add(l);
} else {
StackTraceElement[] ste = threads.get(l).getStackTrace();
if (ste.length > 0) {
DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getDefault());
String df = dateFormat.format(new Date());
log.debug("Thread log - {}", printThreadLog(tt, df));
log.debug("thread stack - {}", printThreadStack(ste, df));
}
}
}
threadUpdate.put(l, next);
}
for (Long l : toRemove) {
threadTracker.remove(l);
threadUpdate.remove(l);
threads.remove(l);
for (Map<Long, Integer> threadLock : locks.values()) {
threadLock.remove(l);
}
}
toRemove.clear();
for (Entry<Long, Integer> it : lockUpdate.entrySet()) {
int val = it.getValue() + 1;
if (val < 60) {
lockUpdate.put(it.getKey(), val);
} else {
toRemove.add(it.getKey()); // free the structure after 60 silent updates
}
}
for (Long l : toRemove) {
lockCount.remove(l);
lockIds.remove(l);
lockThreads.remove(l);
lockUpdate.remove(l);
}
} else { // print status
DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getDefault());
log.error("Deadlock state: {}", printThreadTrackerState(dateFormat.format(new Date())));
//FilePrinter.printError(FilePrinter.DEADLOCK_STATE, "[" + dateFormat.format(new Date()) + "] Presenting current lock path for lockid " + lockId.name() + ".\r\n" + printLockStatus(lockId) + "\r\n-------------------------------");
}
} else {
long tid = Thread.currentThread().getId();
if (lock) {
AtomicInteger c = lockCount.get(lockOid);
if (c == null) {
c = new AtomicInteger(0);
lockCount.put(lockOid, c);
lockIds.put(lockOid, lockId);
lockThreads.put(lockOid, tid);
lockUpdate.put(lockOid, 0);
}
c.incrementAndGet();
List<MonitoredLockType> list = threadTracker.get(tid);
if (list == null) {
list = new ArrayList<>(5);
threadTracker.put(tid, list);
threadUpdate.put(tid, 0);
threads.put(tid, Thread.currentThread());
} else if (list.isEmpty()) {
threadUpdate.put(tid, 0);
}
list.add(lockId);
Map<Long, Integer> threadLock = locks.get(lockId);
if (threadLock == null) {
threadLock = new HashMap<>(5);
locks.put(lockId, threadLock);
}
Integer lc = threadLock.get(tid);
if (lc != null) {
threadLock.put(tid, lc + 1);
} else {
threadLock.put(tid, 1);
}
} else {
AtomicInteger c = lockCount.get(lockOid);
if (c != null) { // thanks BHB for detecting an NPE here
c.decrementAndGet();
}
lockUpdate.put(lockOid, 0);
List<MonitoredLockType> list = threadTracker.get(tid);
for (int i = list.size() - 1; i >= 0; i--) {
if (lockId.equals(list.get(i))) {
list.remove(i);
break;
}
}
Map<Long, Integer> threadLock = locks.get(lockId);
threadLock.put(tid, threadLock.get(tid) - 1);
}
}
} finally {
ttLock.unlock();
}
}
private String printLockStatus(MonitoredLockType lockId) {
String s = "";
for (Long threadid : locks.get(lockId).keySet()) {
for (MonitoredLockType lockid : threadTracker.get(threadid)) {
s += (" " + lockid.name());
}
s += " |\r\n";
}
return s;
}
public void registerThreadTrackerTask() {
threadTrackerSchedule = TimerManager.getInstance().register(() -> accessThreadTracker(true, false, MonitoredLockType.UNDEFINED, -1), 10000, 10000);
}
public void cancelThreadTrackerTask() {
threadTrackerSchedule.cancel(false);
}
}

View File

@@ -1,111 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.audit.locks;
/**
* @author RonanLana
*/
public enum MonitoredLockType {
UNDEFINED,
INTERVAL,
CHARACTER_CHR,
CHARACTER_CPN,
CHARACTER_EFF,
CHARACTER_PET,
CHARACTER_PRT,
CHARACTER_EVT,
CHARACTER_STA,
CLIENT,
CLIENT_ANNOUNCER,
CLIENT_ENCODER,
CLIENT_SESSION,
CLIENT_LOGIN,
BOOK,
ITEM,
INVENTORY,
SRVHANDLER_IDLE,
SRVHANDLER_TEMP,
BUFF_STORAGE,
PLAYER_STORAGE,
PLAYER_DOOR,
SERVER,
SERVER_DISEASES,
SERVER_LOGIN,
SERVER_LOGIN_COORD,
SERVER_WORLDS,
MERCHANT,
CHANNEL,
CHANNEL_EVENTS,
CHANNEL_FACEEXPRS,
CHANNEL_FACESCHDL,
CHANNEL_MOBACTION,
CHANNEL_MOBANIMAT,
CHANNEL_MOBMIST,
CHANNEL_MOBSKILL,
CHANNEL_MOBSTATUS,
CHANNEL_OVTSTATUS,
CHANNEL_OVERALL,
GUILD,
PARTY,
WORLD_PARTY,
WORLD_PARTY_SEARCH_ECHELON,
WORLD_PARTY_SEARCH_QUEUE,
WORLD_PARTY_SEARCH_STORAGE,
WORLD_SRVMESSAGES,
WORLD_PETS,
WORLD_CHARS,
WORLD_CHANNELS,
WORLD_MOUNTS,
WORLD_PSHOPS,
WORLD_MERCHS,
WORLD_MAPOBJS,
WORLD_SAVECHARS,
WORLD_SUGGEST,
EIM,
EIM_PARTY,
EIM_SCRIPT,
EM_LOBBY,
EM_QUEUE,
EM_SCHDL,
EM_START,
CASHSHOP,
VISITOR_PSHOP,
STORAGE,
MOB,
MOB_AGGRO,
MOB_ANI,
MOB_EXT,
MOB_STATI,
MOBSKILL_FACTORY,
PORTAL,
VISITOR_MERCH,
MAP_CHRS,
MAP_OBJS,
MAP_MANAGER,
MAP_ITEM,
MAP_LOOT,
MAP_BOUNDS,
MAP_AGGRO,
MAP_AGGRO_IDLE,
MINIDUNGEON,
REACTOR,
REACTOR_HIT
}

View File

@@ -1,35 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.audit.locks;
/**
* @author RonanLana
*/
public interface MonitoredReadLock {
void lock();
void unlock();
boolean tryLock();
MonitoredReadLock dispose();
}

View File

@@ -1,35 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.audit.locks;
/**
* @author RonanLana
*/
public interface MonitoredReentrantLock {
void lock();
void unlock();
boolean tryLock();
MonitoredReentrantLock dispose();
}

View File

@@ -1,49 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.audit.locks;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author RonanLana
*/
public class MonitoredReentrantReadWriteLock extends ReentrantReadWriteLock {
public final MonitoredLockType id;
public MonitoredReentrantReadWriteLock(MonitoredLockType id) {
super();
this.id = id;
}
public MonitoredReentrantReadWriteLock(MonitoredLockType id, boolean fair) {
super(fair);
this.id = id;
}
@Override
public ReadLock readLock() {
return super.readLock();
}
@Override
public WriteLock writeLock() {
return super.writeLock();
}
}

View File

@@ -1,35 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.audit.locks;
/**
* @author RonanLana
*/
public interface MonitoredWriteLock {
void lock();
void unlock();
boolean tryLock();
MonitoredWriteLock dispose();
}

View File

@@ -1,162 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.audit.locks.active;
import config.YamlConfig;
import net.server.audit.ThreadTracker;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReadLock;
import net.server.audit.locks.MonitoredReentrantReadWriteLock;
import net.server.audit.locks.empty.EmptyReadLock;
import server.TimerManager;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author RonanLana
*/
public class TrackerReadLock extends ReentrantReadWriteLock.ReadLock implements MonitoredReadLock {
private ScheduledFuture<?> timeoutSchedule = null;
private StackTraceElement[] deadlockedState = null;
private final MonitoredLockType id;
private final int hashcode;
private final Lock state = new ReentrantLock(true);
private final AtomicInteger reentrantCount = new AtomicInteger(0);
public TrackerReadLock(MonitoredReentrantReadWriteLock lock) {
super(lock);
this.id = lock.id;
hashcode = this.hashCode();
}
@Override
public void lock() {
if (YamlConfig.config.server.USE_THREAD_TRACKER) {
if (deadlockedState != null) {
DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getDefault());
//FilePrinter.printError(FilePrinter.DEADLOCK_ERROR, "[CRITICAL] " + dateFormat.format(new Date()) + " Deadlock occurred when trying to use the '" + id.name() + "' lock resources:\r\n" + printStackTrace(deadlockedState));
ThreadTracker.getInstance().accessThreadTracker(true, true, id, hashcode);
deadlockedState = null;
}
registerLocking();
}
super.lock();
}
@Override
public void unlock() {
if (YamlConfig.config.server.USE_THREAD_TRACKER) {
unregisterLocking();
}
super.unlock();
}
@Override
public boolean tryLock() {
if (super.tryLock()) {
if (YamlConfig.config.server.USE_THREAD_TRACKER) {
if (deadlockedState != null) {
//FilePrinter.printError(FilePrinter.DEADLOCK_ERROR, "Deadlock occurred when trying to use the '" + id.name() + "' lock resources:\r\n" + printStackTrace(deadlockedState));
ThreadTracker.getInstance().accessThreadTracker(true, true, id, hashcode);
deadlockedState = null;
}
registerLocking();
}
return true;
} else {
return false;
}
}
private void registerLocking() {
state.lock();
try {
ThreadTracker.getInstance().accessThreadTracker(false, true, id, hashcode);
if (reentrantCount.incrementAndGet() == 1) {
final Thread t = Thread.currentThread();
timeoutSchedule = TimerManager.getInstance().schedule(() -> issueDeadlock(t), YamlConfig.config.server.LOCK_MONITOR_TIME);
}
} finally {
state.unlock();
}
}
private void unregisterLocking() {
state.lock();
try {
if (reentrantCount.decrementAndGet() == 0) {
if (timeoutSchedule != null) {
timeoutSchedule.cancel(false);
timeoutSchedule = null;
}
}
ThreadTracker.getInstance().accessThreadTracker(false, false, id, hashcode);
} finally {
state.unlock();
}
}
private void issueDeadlock(Thread t) {
deadlockedState = t.getStackTrace();
//super.unlock();
}
private static String printStackTrace(StackTraceElement[] list) {
String s = "";
for (StackTraceElement stackTraceElement : list) {
s += (" " + stackTraceElement.toString() + "\r\n");
}
return s;
}
@Override
public MonitoredReadLock dispose() {
state.lock();
try {
if (timeoutSchedule != null) {
timeoutSchedule.cancel(false);
timeoutSchedule = null;
}
reentrantCount.set(Integer.MAX_VALUE);
} finally {
state.unlock();
}
//unlock();
return new EmptyReadLock(id);
}
}

View File

@@ -1,166 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.audit.locks.active;
import config.YamlConfig;
import net.server.audit.ThreadTracker;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReentrantLock;
import net.server.audit.locks.empty.EmptyReentrantLock;
import server.TimerManager;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author RonanLana
*/
public class TrackerReentrantLock extends ReentrantLock implements MonitoredReentrantLock {
private ScheduledFuture<?> timeoutSchedule = null;
private StackTraceElement[] deadlockedState = null;
private final MonitoredLockType id;
private final int hashcode;
private final Lock state = new ReentrantLock(true);
private final AtomicInteger reentrantCount = new AtomicInteger(0);
public TrackerReentrantLock(MonitoredLockType id) {
super();
this.id = id;
hashcode = this.hashCode();
}
public TrackerReentrantLock(MonitoredLockType id, boolean fair) {
super(fair);
this.id = id;
hashcode = this.hashCode();
}
@Override
public void lock() {
if (YamlConfig.config.server.USE_THREAD_TRACKER) {
if (deadlockedState != null) {
DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getDefault());
//FilePrinter.printError(FilePrinter.DEADLOCK_ERROR, "[CRITICAL] " + dateFormat.format(new Date()) + " Deadlock occurred when trying to use the '" + id.name() + "' lock resources:\r\n" + printStackTrace(deadlockedState));
ThreadTracker.getInstance().accessThreadTracker(true, true, id, hashcode);
deadlockedState = null;
}
registerLocking();
}
super.lock();
}
@Override
public void unlock() {
if (YamlConfig.config.server.USE_THREAD_TRACKER) {
unregisterLocking();
}
super.unlock();
}
@Override
public boolean tryLock() {
if (super.tryLock()) {
if (YamlConfig.config.server.USE_THREAD_TRACKER) {
if (deadlockedState != null) {
//FilePrinter.printError(FilePrinter.DEADLOCK_ERROR, "Deadlock occurred when trying to use the '" + id.name() + "' lock resources:\r\n" + printStackTrace(deadlockedState));
ThreadTracker.getInstance().accessThreadTracker(true, true, id, hashcode);
deadlockedState = null;
}
registerLocking();
}
return true;
} else {
return false;
}
}
private void registerLocking() {
state.lock();
try {
ThreadTracker.getInstance().accessThreadTracker(false, true, id, hashcode);
if (reentrantCount.incrementAndGet() == 1) {
final Thread t = Thread.currentThread();
timeoutSchedule = TimerManager.getInstance().schedule(() -> issueDeadlock(t), YamlConfig.config.server.LOCK_MONITOR_TIME);
}
} finally {
state.unlock();
}
}
private void unregisterLocking() {
state.lock();
try {
if (reentrantCount.decrementAndGet() == 0) {
if (timeoutSchedule != null) {
timeoutSchedule.cancel(false);
timeoutSchedule = null;
}
}
ThreadTracker.getInstance().accessThreadTracker(false, false, id, hashcode);
} finally {
state.unlock();
}
}
private void issueDeadlock(Thread t) {
deadlockedState = t.getStackTrace();
//super.unlock();
}
private static String printStackTrace(StackTraceElement[] list) {
String s = "";
for (StackTraceElement stackTraceElement : list) {
s += (" " + stackTraceElement.toString() + "\r\n");
}
return s;
}
@Override
public MonitoredReentrantLock dispose() {
state.lock();
try {
if (timeoutSchedule != null) {
timeoutSchedule.cancel(false);
timeoutSchedule = null;
}
reentrantCount.set(Integer.MAX_VALUE);
} finally {
state.unlock();
}
//unlock();
return new EmptyReentrantLock(id);
}
}

View File

@@ -1,162 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.audit.locks.active;
import config.YamlConfig;
import net.server.audit.ThreadTracker;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReentrantReadWriteLock;
import net.server.audit.locks.MonitoredWriteLock;
import net.server.audit.locks.empty.EmptyWriteLock;
import server.TimerManager;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author RonanLana
*/
public class TrackerWriteLock extends ReentrantReadWriteLock.WriteLock implements MonitoredWriteLock {
private ScheduledFuture<?> timeoutSchedule = null;
private StackTraceElement[] deadlockedState = null;
private final MonitoredLockType id;
private final int hashcode;
private final Lock state = new ReentrantLock(true);
private final AtomicInteger reentrantCount = new AtomicInteger(0);
public TrackerWriteLock(MonitoredReentrantReadWriteLock lock) {
super(lock);
this.id = lock.id;
hashcode = this.hashCode();
}
@Override
public void lock() {
if (YamlConfig.config.server.USE_THREAD_TRACKER) {
if (deadlockedState != null) {
DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getDefault());
//FilePrinter.printError(FilePrinter.DEADLOCK_ERROR, "[CRITICAL] " + dateFormat.format(new Date()) + " Deadlock occurred when trying to use the '" + id.name() + "' lock resources:\r\n" + printStackTrace(deadlockedState));
ThreadTracker.getInstance().accessThreadTracker(true, true, id, hashcode);
deadlockedState = null;
}
registerLocking();
}
super.lock();
}
@Override
public void unlock() {
if (YamlConfig.config.server.USE_THREAD_TRACKER) {
unregisterLocking();
}
super.unlock();
}
@Override
public boolean tryLock() {
if (super.tryLock()) {
if (YamlConfig.config.server.USE_THREAD_TRACKER) {
if (deadlockedState != null) {
//FilePrinter.printError(FilePrinter.DEADLOCK_ERROR, "Deadlock occurred when trying to use the '" + id.name() + "' lock resources:\r\n" + printStackTrace(deadlockedState));
ThreadTracker.getInstance().accessThreadTracker(true, true, id, hashcode);
deadlockedState = null;
}
registerLocking();
}
return true;
} else {
return false;
}
}
private void registerLocking() {
state.lock();
try {
ThreadTracker.getInstance().accessThreadTracker(false, true, id, hashcode);
if (reentrantCount.incrementAndGet() == 1) {
final Thread t = Thread.currentThread();
timeoutSchedule = TimerManager.getInstance().schedule(() -> issueDeadlock(t), YamlConfig.config.server.LOCK_MONITOR_TIME);
}
} finally {
state.unlock();
}
}
private void unregisterLocking() {
state.lock();
try {
if (reentrantCount.decrementAndGet() == 0) {
if (timeoutSchedule != null) {
timeoutSchedule.cancel(false);
timeoutSchedule = null;
}
}
ThreadTracker.getInstance().accessThreadTracker(false, false, id, hashcode);
} finally {
state.unlock();
}
}
private void issueDeadlock(Thread t) {
deadlockedState = t.getStackTrace();
//super.unlock();
}
private static String printStackTrace(StackTraceElement[] list) {
String s = "";
for (StackTraceElement stackTraceElement : list) {
s += (" " + stackTraceElement.toString() + "\r\n");
}
return s;
}
@Override
public MonitoredWriteLock dispose() {
state.lock();
try {
if (timeoutSchedule != null) {
timeoutSchedule.cancel(false);
timeoutSchedule = null;
}
reentrantCount.set(Integer.MAX_VALUE);
} finally {
state.unlock();
}
//unlock();
return new EmptyWriteLock(id);
}
}

View File

@@ -1,24 +0,0 @@
package net.server.audit.locks.empty;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
public abstract class AbstractEmptyLock {
protected static String printThreadStack(StackTraceElement[] list) {
DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"); // DRY-code opportunity performed by jtumidanski
dateFormat.setTimeZone(TimeZone.getDefault());
String df = dateFormat.format(new Date());
String s = "\r\n" + df + "\r\n";
for (StackTraceElement stackTraceElement : list) {
s += (" " + stackTraceElement.toString() + "\r\n");
}
s += "----------------------------\r\n\r\n";
return s;
}
}

View File

@@ -1,56 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.audit.locks.empty;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReadLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author RonanLana
*/
public class EmptyReadLock extends AbstractEmptyLock implements MonitoredReadLock {
private static final Logger log = LoggerFactory.getLogger(EmptyReadLock.class);
private final MonitoredLockType id;
public EmptyReadLock(MonitoredLockType type) {
this.id = type;
}
@Override
public void lock() {
log.warn("Captured locking tentative on disposed lock {}: {}", id, printThreadStack(Thread.currentThread().getStackTrace()));
}
@Override
public void unlock() {}
@Override
public boolean tryLock() {
log.warn("Captured try-locking tentative on disposed lock {}: {}", id, printThreadStack(Thread.currentThread().getStackTrace()));
return false;
}
@Override
public MonitoredReadLock dispose() {
return this;
}
}

View File

@@ -1,56 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.audit.locks.empty;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author RonanLana
*/
public class EmptyReentrantLock extends AbstractEmptyLock implements MonitoredReentrantLock {
private static final Logger log = LoggerFactory.getLogger(EmptyReentrantLock.class);
private final MonitoredLockType id;
public EmptyReentrantLock(MonitoredLockType type) {
this.id = type;
}
@Override
public void lock() {
log.warn("Captured locking tentative on disposed lock {}: {}", id, printThreadStack(Thread.currentThread().getStackTrace()));
}
@Override
public void unlock() {}
@Override
public boolean tryLock() {
log.warn("Captured try-locking tentative on disposed lock {}: {}", id, printThreadStack(Thread.currentThread().getStackTrace()));
return false;
}
@Override
public MonitoredReentrantLock dispose() {
return this;
}
}

View File

@@ -1,56 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.audit.locks.empty;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredWriteLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author RonanLana
*/
public class EmptyWriteLock extends AbstractEmptyLock implements MonitoredWriteLock {
private static final Logger log = LoggerFactory.getLogger(EmptyWriteLock.class);
private final MonitoredLockType id;
public EmptyWriteLock(MonitoredLockType type) {
this.id = type;
}
@Override
public void lock() {
log.warn("Captured locking tentative on disposed lock {}: {}", id, printThreadStack(Thread.currentThread().getStackTrace()));
}
@Override
public void unlock() {}
@Override
public boolean tryLock() {
log.warn("Captured try-locking tentative on dispsoed lock {}: {}", id, printThreadStack(Thread.currentThread().getStackTrace()));
return false;
}
@Override
public MonitoredWriteLock dispose() {
return this;
}
}

View File

@@ -21,8 +21,6 @@ package net.server.services;
import config.YamlConfig;
import net.server.Server;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReentrantLock;
import server.TimerManager;
import tools.Pair;
@@ -38,18 +36,18 @@ import java.util.concurrent.locks.ReentrantLock;
public abstract class BaseScheduler {
private int idleProcs = 0;
private final List<SchedulerListener> listeners = new LinkedList<>();
private final List<MonitoredReentrantLock> externalLocks = new LinkedList<>();
private final List<Lock> externalLocks = new LinkedList<>();
private final Map<Object, Pair<Runnable, Long>> registeredEntries = new HashMap<>();
private ScheduledFuture<?> schedulerTask = null;
private final Lock schedulerLock = new ReentrantLock(true);
private final Runnable monitorTask = () -> runBaseSchedule();
protected BaseScheduler(MonitoredLockType lockType) {
protected BaseScheduler() {
}
// NOTE: practice EXTREME caution when adding external locks to the scheduler system, if you don't know what you're doing DON'T USE THIS.
protected BaseScheduler(MonitoredLockType lockType, List<MonitoredReentrantLock> extLocks) {
protected BaseScheduler(List<Lock> extLocks) {
externalLocks.addAll(extLocks);
}
@@ -58,22 +56,12 @@ public abstract class BaseScheduler {
}
private void lockScheduler() {
if (!externalLocks.isEmpty()) {
for (MonitoredReentrantLock l : externalLocks) {
l.lock();
}
}
externalLocks.forEach(Lock::lock);
schedulerLock.lock();
}
private void unlockScheduler() {
if (!externalLocks.isEmpty()) {
for (MonitoredReentrantLock l : externalLocks) {
l.unlock();
}
}
externalLocks.forEach(Lock::unlock);
schedulerLock.unlock();
}

View File

@@ -20,7 +20,6 @@
package net.server.services.task.channel;
import config.YamlConfig;
import net.server.audit.locks.MonitoredLockType;
import net.server.services.BaseScheduler;
import net.server.services.BaseService;
@@ -53,10 +52,6 @@ public class EventService extends BaseService {
private class EventScheduler extends BaseScheduler {
public EventScheduler() {
super(MonitoredLockType.CHANNEL_EVENTS);
}
public void registerDelayedAction(Runnable runAction, long delay) {
registerEntry(runAction, runAction, delay);
}

View File

@@ -20,7 +20,6 @@
package net.server.services.task.channel;
import config.YamlConfig;
import net.server.audit.locks.MonitoredLockType;
import net.server.services.BaseScheduler;
import net.server.services.BaseService;
@@ -65,8 +64,6 @@ public class MobAnimationService extends BaseService {
private final Lock animationLock = new ReentrantLock(true);
public MobAnimationScheduler() {
super(MonitoredLockType.CHANNEL_MOBACTION);
super.addListener((toRemove, update) -> {
animationLock.lock();
try {

View File

@@ -20,7 +20,6 @@
package net.server.services.task.channel;
import config.YamlConfig;
import net.server.audit.locks.MonitoredLockType;
import net.server.services.BaseScheduler;
import net.server.services.BaseService;
@@ -53,10 +52,6 @@ public class MobClearSkillService extends BaseService {
private class MobClearSkillScheduler extends BaseScheduler {
public MobClearSkillScheduler() {
super(MonitoredLockType.CHANNEL_MOBSKILL);
}
public void registerClearSkillAction(Runnable runAction, long delay) {
registerEntry(runAction, runAction, delay);
}

View File

@@ -20,7 +20,6 @@
package net.server.services.task.channel;
import config.YamlConfig;
import net.server.audit.locks.MonitoredLockType;
import net.server.services.BaseScheduler;
import net.server.services.BaseService;
@@ -53,10 +52,6 @@ public class MobMistService extends BaseService {
private class MobMistScheduler extends BaseScheduler {
public MobMistScheduler() {
super(MonitoredLockType.CHANNEL_MOBMIST);
}
public void registerMistCancelAction(Runnable runAction, long delay) {
registerEntry(runAction, runAction, delay);
}

View File

@@ -21,7 +21,6 @@ package net.server.services.task.channel;
import client.status.MonsterStatusEffect;
import config.YamlConfig;
import net.server.audit.locks.MonitoredLockType;
import net.server.services.BaseScheduler;
import net.server.services.BaseService;
@@ -93,8 +92,6 @@ public class MobStatusService extends BaseService {
}
public MobStatusScheduler() {
super(MonitoredLockType.CHANNEL_MOBSTATUS);
super.addListener((toRemove, update) -> {
List<Runnable> toRun = new ArrayList<>();

View File

@@ -20,7 +20,6 @@
package net.server.services.task.channel;
import config.YamlConfig;
import net.server.audit.locks.MonitoredLockType;
import net.server.services.BaseScheduler;
import net.server.services.BaseService;
@@ -58,10 +57,6 @@ public class OverallService extends BaseService { // thanks Alex for suggestin
public class OverallScheduler extends BaseScheduler {
public OverallScheduler() {
super(MonitoredLockType.CHANNEL_OVERALL);
}
public void registerDelayedAction(Runnable runAction, long delay) {
registerEntry(runAction, runAction, delay);
}

View File

@@ -19,7 +19,6 @@
*/
package net.server.services.task.world;
import net.server.audit.locks.MonitoredLockType;
import net.server.services.BaseScheduler;
import net.server.services.BaseService;
@@ -44,10 +43,6 @@ public class CharacterSaveService extends BaseService {
private class CharacterSaveScheduler extends BaseScheduler {
public CharacterSaveScheduler() {
super(MonitoredLockType.WORLD_SAVECHARS);
}
public void registerSaveCharacter(Integer characterId, Runnable runAction) {
registerEntry(characterId, runAction, 0);
}

View File

@@ -1,33 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.task;
import net.server.audit.LockCollector;
/**
* @author Ronan
* @info Thread responsible for expiring locks signalized for dispose.
*/
public class ReleaseLockTask implements Runnable {
@Override
public void run() {
LockCollector.getInstance().runLockCollector();
}
}