diff --git a/dist/MapleSolaxia.jar b/dist/MapleSolaxia.jar
index 6255e4a8d2..192a00441e 100644
Binary files a/dist/MapleSolaxia.jar and b/dist/MapleSolaxia.jar differ
diff --git a/docs/feature_list.txt b/docs/feature_list.txt
index 5e95145299..8c3dd52479 100644
--- a/docs/feature_list.txt
+++ b/docs/feature_list.txt
@@ -103,5 +103,6 @@ Project:
* Fixed/added some missing packets for MoveEnvironment, summons and others.
* Reviewed many Java object aspects that needed concurrency protection.
* Heavily reviewed future task management inside the project. Way less trivial schedules are spawned now, relieving task overload on the TimerManager.
+* ThreadTracker: embedded auditing tool for run-time deadlock scanning throughout the server source (relies heavily on memory usage, designed only for debugging purposes).
---------------------------
\ No newline at end of file
diff --git a/docs/mychanges_ptbr.txt b/docs/mychanges_ptbr.txt
index a15f24b3f4..1a5d0290ca 100644
--- a/docs/mychanges_ptbr.txt
+++ b/docs/mychanges_ptbr.txt
@@ -669,4 +669,9 @@ Corrigido GM shop sendo liberado pra jogadores em Amherst.
13 - 14 Novembro 2017,
Modificado ID de jogador agora começando em 20,000,000, evitando assim clash de id de jogador (que tb representa seu OID) com OIDs de objetos do mapa.
Nova ferramenta: MapleSkillMakerFetcher. A ferramenta lê info pertinente às descrições de itens feitos pelo Maker e as compila numa tabela, pra ser usada na DB.
-Corrigida questline de mounts e skills de Aran.
\ No newline at end of file
+Corrigida questline de mounts e skills de Aran.
+
+15 Novembro 2017,
+Implementado sistema de auditoria de deadlocks: ThreadTracker.
+Corrigido bug onde jogadores recebem dano de mobs de mapas anteriores (que acarretavam em problemas com OID e natureza dos objetos).
+Corrigido alguns problemas com exceção sendo lançada ao tentar desligar o server.
\ No newline at end of file
diff --git a/nbproject/private/private.xml b/nbproject/private/private.xml
index 3287222d26..11ac3455f7 100644
--- a/nbproject/private/private.xml
+++ b/nbproject/private/private.xml
@@ -2,6 +2,9 @@
-
+
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/client/command/Commands.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/constants/ServerConstants.java
+
diff --git a/scripts/quest/2314.js b/scripts/quest/2314.js
index ea35393d55..bd9a8ac7c2 100644
--- a/scripts/quest/2314.js
+++ b/scripts/quest/2314.js
@@ -22,7 +22,7 @@ function start(mode, type, selection) {
}
}
if (status == 0)
- qm.sendYesNo("In order to rescue the princess, you must first navigate the Mushroom Forest. King Pepe set up a powerful barrier forbidding anyone from entering the castle. Please investigate this matter for us.");
+ qm.sendAcceptDecline("In order to rescue the princess, you must first navigate the Mushroom Forest. King Pepe set up a powerful barrier forbidding anyone from entering the castle. Please investigate this matter for us.");
if (status == 1)
qm.sendNext("You'll run into the barrier at the Mushroom Forest by heading east of where you are standing right now. Please be careful. I hear that the area is infested with crazy, fear-inducing monsters.");
if(status == 2){
diff --git a/sql/db_drops.sql b/sql/db_drops.sql
index 135ce5bbba..7a5cfa5f09 100644
--- a/sql/db_drops.sql
+++ b/sql/db_drops.sql
@@ -18793,7 +18793,6 @@ USE `maplesolaxia`;
(3400004,4000542,1,1,0,400000),
(3400005,4032508,1,1,2273,999999),
(3400006,4000543,1,1,0,400000),
-(3400007,4032508,1,1,2273,999999),
(3400008,4000544,1,1,0,400000),
(4300001,1302009,1,1,0,700),
(4300001,1312007,1,1,0,700),
@@ -18939,7 +18938,7 @@ USE `maplesolaxia`;
(4300006,4000525,1,1,0,600000),
(4300006,4004000,1,1,0,10000),
(4300006,4020008,1,1,0,9000),
-(4300006,4032506,1,1,2277,999999),
+(4300006,4032506,1,1,2277,80000),
(4300007,1302009,1,1,0,700),
(4300007,1312007,1,1,0,700),
(4300007,1322016,1,1,0,700),
@@ -18976,7 +18975,7 @@ USE `maplesolaxia`;
(4300007,4000526,1,1,0,600000),
(4300007,4004000,1,1,0,10000),
(4300007,4020008,1,1,0,9000),
-(4300007,4032506,1,1,2277,999999),
+(4300007,4032506,1,1,2277,80000),
(4300008,1302009,1,1,0,700),
(4300008,1312007,1,1,0,700),
(4300008,1322016,1,1,0,700),
@@ -19013,7 +19012,7 @@ USE `maplesolaxia`;
(4300008,4000527,1,1,0,400000),
(4300008,4004000,1,1,0,10000),
(4300008,4020008,1,1,0,9000),
-(4300008,4032506,1,1,2277,999999),
+(4300008,4032506,1,1,2277,80000),
(4300009,1302009,1,1,0,700),
(4300009,1312007,1,1,0,700),
(4300009,1322016,1,1,0,700),
@@ -19207,7 +19206,6 @@ USE `maplesolaxia`;
(3400004,0,60,80,0,400000),
(3400005,0,80,85,0,400000),
(3400006,0,80,90,0,400000),
-(3400007,0,90,100,0,400000),
(3400008,0,100,110,0,400000),
(4300001,0,100,120,0,400000),
(4300003,0,100,120,0,400000),
@@ -19792,7 +19790,28 @@ USE `maplesolaxia`;
(2230111, 4032147, 1, 1, 20723, 40000),
(9300378, 4001272, 1, 1, 0, 400000),
(9300378, 4032324, 1, 1, 21736, 40000),
-(9300344, 4032322, 1, 1, 21731, 999999);
+(9300344, 4032322, 1, 1, 21731, 999999),
+(3400008, 1302008, 1, 1, 0, 8500),
+(3400008, 1412004, 1, 1, 0, 8500),
+(3400008, 1422007, 1, 1, 0, 8500),
+(3400008, 1442009, 1, 1, 0, 8500),
+(3400008, 1332010, 1, 1, 0, 8500),
+(3400008, 1372001, 1, 1, 0, 8500),
+(3400008, 1382002, 1, 1, 0, 8500),
+(3400008, 1002013, 1, 1, 0, 8500),
+(3400008, 1002152, 1, 1, 0, 8500),
+(3400008, 1061047, 1, 1, 0, 8500),
+(3400008, 1072090, 1, 1, 0, 8500),
+(3400008, 1002137, 1, 1, 0, 8500),
+(3400008, 1040023, 1, 1, 0, 8500),
+(3400008, 1040072, 1, 1, 0, 8500),
+(3400008, 1060062, 1, 1, 0, 8500),
+(3400008, 1082049, 1, 1, 0, 8500),
+(3400008, 1082072, 1, 1, 0, 8500),
+(3400008, 1072081, 1, 1, 0, 8500),
+(3400008, 1332031, 1, 1, 0, 8500),
+(3400008, 1482003, 1, 1, 0, 8500),
+(3400008, 4032508, 1, 1, 2273, 80000);
# (dropperid, itemid, minqty, maxqty, questid, chance)
diff --git a/src/client/MapleCharacter.java b/src/client/MapleCharacter.java
index 7dae8d9148..6acef3a1b2 100644
--- a/src/client/MapleCharacter.java
+++ b/src/client/MapleCharacter.java
@@ -45,7 +45,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Comparator;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
@@ -154,6 +154,7 @@ import constants.skills.ThunderBreaker;
import net.server.channel.handlers.PartyOperationHandler;
import scripting.item.ItemScriptManager;
import server.maps.MapleMapItem;
+import tools.locks.MonitoredEnums;
public class MapleCharacter extends AbstractAnimatedMapleMapObject {
private static NumberFormat nf = new DecimalFormat("#,###,###,###");
@@ -270,10 +271,10 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
private ScheduledFuture> extraRecoveryTask = null;
private ScheduledFuture> chairRecoveryTask = null;
private ScheduledFuture> pendantOfSpirit = null; //1122017
- private Lock chrLock = new ReentrantLock(true);
- private Lock effLock = new ReentrantLock(true);
- private Lock petLock = new ReentrantLock(true); // for quest tasks as well
- private Lock prtLock = new ReentrantLock();
+ private Lock chrLock = new MonitoredReentrantLock(MonitoredEnums.CHR, true);
+ private Lock effLock = new MonitoredReentrantLock(MonitoredEnums.EFF, true);
+ private Lock petLock = new MonitoredReentrantLock(MonitoredEnums.PET, true); // for quest tasks as well
+ private Lock prtLock = new MonitoredReentrantLock(MonitoredEnums.PRT);
private Map> excluded = new LinkedHashMap<>();
private Set excludedItems = new LinkedHashSet<>();
private List crushRings = new ArrayList<>();
diff --git a/src/client/MapleClient.java b/src/client/MapleClient.java
index 6b3068888c..2880cb1784 100644
--- a/src/client/MapleClient.java
+++ b/src/client/MapleClient.java
@@ -42,7 +42,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import javax.script.ScriptEngine;
@@ -77,6 +77,7 @@ import tools.FilePrinter;
import tools.HexTool;
import tools.MapleAESOFB;
import tools.MaplePacketCreator;
+import tools.locks.MonitoredEnums;
public class MapleClient {
@@ -108,7 +109,7 @@ public class MapleClient {
private int picattempt = 0;
private byte gender = -1;
private boolean disconnecting = false;
- private final Lock lock = new ReentrantLock(true);
+ private final Lock lock = new MonitoredReentrantLock(MonitoredEnums.CLIENT, true);
private int votePoints;
private int voteTime = -1;
private long lastNpcClick;
@@ -832,7 +833,7 @@ public class MapleClient {
removePlayer();
player.saveCooldowns();
- player.saveToDB();
+ player.saveToDB(true);
player = null;
return;
diff --git a/src/client/MonsterBook.java b/src/client/MonsterBook.java
index ef76f911c7..7dc541bdbb 100644
--- a/src/client/MonsterBook.java
+++ b/src/client/MonsterBook.java
@@ -31,16 +31,17 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import tools.DatabaseConnection;
import tools.MaplePacketCreator;
+import tools.locks.MonitoredEnums;
public final class MonsterBook {
private int specialCard = 0;
private int normalCard = 0;
private int bookLevel = 1;
private Map cards = new LinkedHashMap<>();
- private Lock lock = new ReentrantLock();
+ private Lock lock = new MonitoredReentrantLock(MonitoredEnums.BOOK);
private Set> getCardSet() {
lock.lock();
diff --git a/src/client/command/Commands.java b/src/client/command/Commands.java
index 2215d6df65..c6c33e124a 100644
--- a/src/client/command/Commands.java
+++ b/src/client/command/Commands.java
@@ -346,7 +346,7 @@ public class Commands {
case "time":
DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
- dateFormat.setTimeZone(TimeZone.getTimeZone("-GMT3"));
+ dateFormat.setTimeZone(TimeZone.getTimeZone(ServerConstants.TIMEZONE));
player.yellowMessage("Solaxia Server Time: " + dateFormat.format(new Date()));
break;
@@ -741,7 +741,7 @@ public class Commands {
player.yellowMessage("Players on this map:");
for (MapleMapObject mmo : player.getMap().getPlayers()) {
MapleCharacter chr = (MapleCharacter) mmo;
- player.dropMessage(5, ">> " + chr.getName());
+ player.dropMessage(5, ">> " + chr.getName() + " - " + chr.getId() + " - Oid: " + chr.getObjectId());
}
player.yellowMessage("NPCs on this map:");
for (MapleMapObject npcs : player.getMap().getMapObjects()) {
@@ -755,7 +755,7 @@ public class Commands {
if (mobs instanceof MapleMonster) {
MapleMonster mob = (MapleMonster) mobs;
if(mob.isAlive()){
- player.dropMessage(5, ">> " + mob.getName() + " - " + mob.getId());
+ player.dropMessage(5, ">> " + mob.getName() + " - " + mob.getId() + " - Oid: " + mob.getObjectId());
}
}
}
diff --git a/src/client/inventory/ItemFactory.java b/src/client/inventory/ItemFactory.java
index 6812e63554..74bf995541 100644
--- a/src/client/inventory/ItemFactory.java
+++ b/src/client/inventory/ItemFactory.java
@@ -27,9 +27,11 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.Lock;
+import tools.locks.MonitoredReentrantLock;
import tools.DatabaseConnection;
import tools.Pair;
+import tools.locks.MonitoredEnums;
/**
*
@@ -45,7 +47,7 @@ public enum ItemFactory {
MERCHANT(6, false);
private final int value;
private final boolean account;
- private static final ReentrantLock lock = new ReentrantLock(true);
+ private static final Lock lock = new MonitoredReentrantLock(MonitoredEnums.ITEM, true);
private ItemFactory(int value, boolean account) {
this.value = value;
diff --git a/src/client/inventory/MapleInventory.java b/src/client/inventory/MapleInventory.java
index cf0a925d24..f9a02fa141 100644
--- a/src/client/inventory/MapleInventory.java
+++ b/src/client/inventory/MapleInventory.java
@@ -30,7 +30,7 @@ import java.util.List;
import java.util.Map.Entry;
import java.util.Map;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import tools.Pair;
import client.MapleCharacter;
@@ -39,6 +39,7 @@ import constants.ItemConstants;
import server.MapleItemInformationProvider;
import server.MapleInventoryManipulator;
import tools.FilePrinter;
+import tools.locks.MonitoredEnums;
/**
*
@@ -50,7 +51,7 @@ public class MapleInventory implements Iterable- {
private byte slotLimit;
private MapleInventoryType type;
private boolean checked = false;
- private Lock lock = new ReentrantLock(true);
+ private Lock lock = new MonitoredReentrantLock(MonitoredEnums.INVENTORY, true);
public MapleInventory(MapleCharacter mc, MapleInventoryType type, byte slotLimit) {
this.owner = mc;
diff --git a/src/constants/ServerConstants.java b/src/constants/ServerConstants.java
index 1ff8120359..776921eb96 100644
--- a/src/constants/ServerConstants.java
+++ b/src/constants/ServerConstants.java
@@ -8,10 +8,12 @@ public class ServerConstants {
public static String DB_URL = "";
public static String DB_USER = "";
public static String DB_PASS = "";
- public static final boolean DB_EXPERIMENTAL_POOL = true; //[EXPERIMENTAL] Installs a connection pool to hub DB connections. Set false to default.
+ public static final boolean DB_EXPERIMENTAL_POOL = true; //[EXPERIMENTAL] Installs a connection pool to hub DB connections. Set false to default.
+ public static final boolean USE_THREAD_TRACKER = true; //[WARNING] This deadlock auditing thing will bloat the memory as fast as the time frame one takes to lose track of a raindrop on a tempesting day. Only for debug purposes.
//World And Version
public static short VERSION = 83;
+ public static String TIMEZONE = "-GMT3";
public static String[] WORLD_NAMES = {"Scania", "Bera", "Broa", "Windia", "Khaini", "Bellocan", "Mardia", "Kradia", "Yellonde", "Demethos", "Galicia", "El Nido", "Zenith", "Arcenia", "Kastia", "Judis", "Plana", "Kalluna", "Stius", "Croa", "Medere"};
//Login Configuration
@@ -85,6 +87,7 @@ public class ServerConstants {
//Dangling Items Configuration
public static final int ITEM_EXPIRE_TIME = 3 * 60 * 1000; //Time before items start disappearing. Recommended to be set up to 3 minutes.
public static final int ITEM_MONITOR_TIME = 5 * 60 * 1000; //Interval between item monitoring tasks on maps, which checks for dangling (null) item objects on the map item history.
+ public static final int LOCK_MONITOR_TIME = 30 * 1000; //Waiting time for a lock to be released. If it reach timed out, a critical server deadlock has made present.
public static final int ITEM_EXPIRE_CHECK = 10 * 1000; //Interval between item expiring tasks on maps, which checks and makes disappear expired items.
public static final int ITEM_LIMIT_ON_MAP = 200; //Max number of items allowed on a map.
diff --git a/src/net/MapleServerHandler.java b/src/net/MapleServerHandler.java
index 7107ade5a1..af55d9a697 100644
--- a/src/net/MapleServerHandler.java
+++ b/src/net/MapleServerHandler.java
@@ -43,13 +43,14 @@ import client.MapleClient;
import constants.ServerConstants;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import java.util.concurrent.ScheduledFuture;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import server.TimerManager;
+import tools.locks.MonitoredEnums;
public class MapleServerHandler extends IoHandlerAdapter {
@@ -58,8 +59,8 @@ public class MapleServerHandler extends IoHandlerAdapter {
private static final SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HH:mm");
private static AtomicLong sessionId = new AtomicLong(7777);
- private Lock idleLock = new ReentrantLock(true);
- private Lock tempLock = new ReentrantLock(true);
+ private Lock idleLock = new MonitoredReentrantLock(MonitoredEnums.SHANDLER_IDLE, true);
+ private Lock tempLock = new MonitoredReentrantLock(MonitoredEnums.SHANDLER_TEMP, true);
private Map idleSessions = new HashMap<>(100);
private Map tempIdleSessions = new HashMap<>();
private ScheduledFuture> idleManager = null;
diff --git a/src/net/mina/MaplePacketEncoder.java b/src/net/mina/MaplePacketEncoder.java
index e8c30a96e1..2024efd0fc 100644
--- a/src/net/mina/MaplePacketEncoder.java
+++ b/src/net/mina/MaplePacketEncoder.java
@@ -35,27 +35,28 @@ public class MaplePacketEncoder implements ProtocolEncoder {
public void encode(final IoSession session, final Object message, final ProtocolEncoderOutput out) throws Exception {
final MapleClient client = (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY);
- if (client != null) {
- final MapleAESOFB send_crypto = client.getSendCrypto();
- final byte[] input = (byte[]) message;
- final byte[] unencrypted = new byte[input.length];
- System.arraycopy(input, 0, unencrypted, 0, input.length);
- final byte[] ret = new byte[unencrypted.length + 4];
- final byte[] header = send_crypto.getPacketHeader(unencrypted.length);
- MapleCustomEncryption.encryptData(unencrypted);
-
+ try {
client.lockClient();
try {
+ final MapleAESOFB send_crypto = client.getSendCrypto();
+ final byte[] input = (byte[]) message;
+ final byte[] unencrypted = new byte[input.length];
+ System.arraycopy(input, 0, unencrypted, 0, input.length);
+ final byte[] ret = new byte[unencrypted.length + 4];
+ final byte[] header = send_crypto.getPacketHeader(unencrypted.length);
+ MapleCustomEncryption.encryptData(unencrypted);
+
send_crypto.crypt(unencrypted);
System.arraycopy(header, 0, ret, 0, 4);
System.arraycopy(unencrypted, 0, ret, 4, unencrypted.length);
+
out.write(IoBuffer.wrap(ret));
} finally {
client.unlockClient();
}
// System.arraycopy(unencrypted, 0, ret, 4, unencrypted.length);
// out.write(ByteBuffer.wrap(ret));
- } else {
+ } catch (NullPointerException npe) {
out.write(IoBuffer.wrap(((byte[]) message)));
}
}
diff --git a/src/net/server/PlayerBuffStorage.java b/src/net/server/PlayerBuffStorage.java
index 1de55ec044..d1b8d3bca7 100644
--- a/src/net/server/PlayerBuffStorage.java
+++ b/src/net/server/PlayerBuffStorage.java
@@ -25,7 +25,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredEnums;
+import tools.locks.MonitoredReentrantLock;
/**
*
@@ -33,7 +34,7 @@ import java.util.concurrent.locks.ReentrantLock;
*/
public class PlayerBuffStorage {
private int id = (int) (Math.random() * 100);
- private final Lock lock = new ReentrantLock(true);
+ private final Lock lock = new MonitoredReentrantLock(MonitoredEnums.BUFF_STORAGE, true);
private Map> buffs = new HashMap<>();
public void addBuffsToStorage(int chrid, List toStore) {
diff --git a/src/net/server/PlayerStorage.java b/src/net/server/PlayerStorage.java
index 3b0c807f33..59093ea8e2 100644
--- a/src/net/server/PlayerStorage.java
+++ b/src/net/server/PlayerStorage.java
@@ -26,12 +26,14 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
+import tools.locks.MonitoredReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
+import tools.locks.MonitoredEnums;
public class PlayerStorage {
- private final ReentrantReadWriteLock locks = new ReentrantReadWriteLock(true);
+ private final ReentrantReadWriteLock locks = new MonitoredReentrantReadWriteLock(MonitoredEnums.PLAYER_STORAGE, true);
private final ReadLock rlock = locks.readLock();
private final WriteLock wlock = locks.writeLock();
private final Map storage = new LinkedHashMap<>();
diff --git a/src/net/server/Server.java b/src/net/server/Server.java
index 45d71ac0da..d33d7a241d 100644
--- a/src/net/server/Server.java
+++ b/src/net/server/Server.java
@@ -33,15 +33,16 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.LinkedHashMap;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import java.util.concurrent.locks.Lock;
+import java.util.concurrent.ScheduledFuture;
import net.MapleServerHandler;
import net.mina.MapleCodecFactory;
@@ -70,11 +71,13 @@ import client.SkillFactory;
import constants.ItemConstants;
import constants.ServerConstants;
import java.util.Calendar;
+import net.server.audit.ThreadTracker;
import server.quest.MapleQuest;
+import tools.locks.MonitoredEnums;
public class Server implements Runnable {
private static final Set activeFly = new HashSet<>();
- private static final Map couponRates = new LinkedHashMap<>();
+ private static final Map couponRates = new HashMap<>(30);
private static final List activeCoupons = new LinkedList<>();
private IoAcceptor acceptor;
@@ -83,11 +86,12 @@ public class Server implements Runnable {
private final Properties subnetInfo = new Properties();
private static Server instance = null;
private List> worldRecommendedList = new LinkedList<>();
- private final Map guilds = new LinkedHashMap<>();
- private final Map inLoginState = new LinkedHashMap<>();
- private final Lock srvLock = new ReentrantLock();
+ private final Map guilds = new HashMap<>(100);
+ private final Map inLoginState = new HashMap<>(100);
+ private final Lock srvLock = new MonitoredReentrantLock(MonitoredEnums.SERVER);
private final PlayerBuffStorage buffStorage = new PlayerBuffStorage();
- private final Map alliances = new LinkedHashMap<>();
+ private final Map alliances = new HashMap<>(100);
+
private boolean online = false;
public static long uptime = System.currentTimeMillis();
@@ -301,7 +305,9 @@ public class Server implements Runnable {
timeToTake = System.currentTimeMillis();
MapleQuest.loadAllQuest();
System.out.println("Quest loaded in " + ((System.currentTimeMillis() - timeToTake) / 1000.0) + " seconds\r\n");
-
+
+ if(ServerConstants.USE_THREAD_TRACKER) ThreadTracker.getInstance().registerThreadTrackerTask();
+
try {
Integer worldCount = Math.min(ServerConstants.WORLD_NAMES.length, Integer.parseInt(p.getProperty("worlds")));
@@ -317,7 +323,7 @@ public class Server implements Runnable {
worldRecommendedList.add(new Pair<>(i, p.getProperty("whyamirecommended" + i)));
worlds.add(world);
- channels.add(new LinkedHashMap());
+ channels.add(new HashMap());
for (int j = 0; j < Integer.parseInt(p.getProperty("channels" + i)); j++) {
int channelid = j + 1;
Channel channel = new Channel(i, channelid);
@@ -807,6 +813,8 @@ public class Server implements Runnable {
}
}
}*/
+
+ if(ServerConstants.USE_THREAD_TRACKER) ThreadTracker.getInstance().cancelThreadTrackerTask();
TimerManager.getInstance().purge();
TimerManager.getInstance().stop();
diff --git a/src/net/server/audit/ThreadTracker.java b/src/net/server/audit/ThreadTracker.java
new file mode 100644
index 0000000000..d79b3d3f77
--- /dev/null
+++ b/src/net/server/audit/ThreadTracker.java
@@ -0,0 +1,281 @@
+/*
+ * This file is part of the MapleSolaxiaV2 Maple Story Server
+ *
+ * Copyright (C) 2017 RonanLana
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package net.server.audit;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+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 server.TimerManager;
+import tools.FilePrinter;
+import tools.locks.MonitoredEnums;
+import constants.ServerConstants;
+
+/**
+ *
+ * @author RonanLana
+ *
+ * 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 ThreadTracker instance = null;
+ private final Lock ttLock = new ReentrantLock(true);
+
+ private final Map> threadTracker = new HashMap<>();
+ private final Map threadUpdate = new HashMap<>();
+ private final Map threads = new HashMap<>();
+
+ private final Map lockCount = new HashMap<>();
+ private final Map lockIds = new HashMap<>();
+ private final Map lockThreads = new HashMap<>();
+ private final Map lockUpdate = new HashMap<>();
+
+ private final Map> locks = new HashMap<>();
+ ScheduledFuture> threadTrackerSchedule;
+
+ private String printThreadTrackerState(String dateFormat) {
+
+ Map> lockValues = new HashMap<>();
+ Set executingThreads = new HashSet<>();
+
+ for(Map.Entry lock : lockCount.entrySet()) {
+ if(lock.getValue().get() != 0) {
+ executingThreads.add(lockThreads.get(lock.getKey()));
+
+ MonitoredEnums lockId = lockIds.get(lock.getKey());
+ List list = lockValues.get(lockId);
+
+ if(list == null) {
+ list = new ArrayList<>();
+ lockValues.put(lockId, list);
+ }
+
+ list.add(lock.getValue().get());
+ }
+ }
+
+
+ String s = "----------------------------\r\n" + dateFormat + "\r\n ";
+ s += "Lock-thread usage count:";
+ for(Map.Entry> 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(MonitoredEnums lockid : threadTracker.get(tid)) {
+ s += (lockid.name() + " ");
+ }
+ s += "|";
+ }
+
+ s += "\r\n\r\n";
+
+ return s;
+ }
+
+ private static String printThreadLog(List stillLockedPath, String dateFormat) {
+ String s = "----------------------------\r\n" + dateFormat + "\r\n ";
+ for(MonitoredEnums 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(int i = 0; i < list.length; i++) {
+ s += (" " + list[i].toString() + "\r\n");
+ }
+
+ return s;
+ }
+
+ public void accessThreadTracker(boolean update, boolean lock, MonitoredEnums lockId, long lockOid) {
+ ttLock.lock();
+ try {
+ if(update) {
+ if(!lock) { // update tracker
+ List toRemove = new ArrayList<>();
+
+ for(Long l : threadUpdate.keySet()) {
+ int next = threadUpdate.get(l) + 1;
+ if(next == 4) {
+ List tt = threadTracker.get(l);
+
+ if(tt.isEmpty()) {
+ toRemove.add(l);
+ } else {
+ DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
+ dateFormat.setTimeZone(TimeZone.getTimeZone(ServerConstants.TIMEZONE));
+ String df = dateFormat.format(new Date());
+
+ FilePrinter.print(FilePrinter.DEADLOCK_LOCKS, printThreadLog(tt, df));
+ FilePrinter.print(FilePrinter.DEADLOCK_STACK, printThreadStack(threads.get(l).getStackTrace(), df));
+ }
+ }
+
+ threadUpdate.put(l, next);
+ }
+
+ for(Long l : toRemove) {
+ threadTracker.remove(l);
+ threadUpdate.remove(l);
+ threads.remove(l);
+
+ for(Map threadLock : locks.values()) {
+ threadLock.remove(l);
+ }
+ }
+
+ toRemove.clear();
+
+ for(Entry it : lockUpdate.entrySet()) {
+ byte val = (byte)(it.getValue() + 1);
+
+ if(val < 60) { // free the structure after 60 silent updates
+ lockUpdate.put(it.getKey(), val);
+ } else {
+ toRemove.add(it.getKey());
+ }
+ }
+
+ 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.getTimeZone(ServerConstants.TIMEZONE));
+
+ FilePrinter.printError(FilePrinter.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-------------------------------\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, (byte) 0);
+ }
+ c.incrementAndGet();
+
+ List list = threadTracker.get(tid);
+ if(list == null) {
+ list = new ArrayList<>(20);
+ threadTracker.put(tid, list);
+ threadUpdate.put(tid, 0);
+ threads.put(tid, Thread.currentThread());
+ }
+ list.add(lockId);
+
+ Map 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);
+ c.decrementAndGet();
+ lockUpdate.put(lockOid, (byte) 0);
+
+ List list = threadTracker.get(tid);
+ for(int i = list.size() - 1; i >= 0; i--) {
+ if(lockId.getValue() == list.get(i).getValue()) {
+ list.remove(i);
+ break;
+ }
+ }
+
+ Map threadLock = locks.get(lockId);
+ threadLock.put(tid, threadLock.get(tid) - 1);
+ }
+ }
+ } finally {
+ ttLock.unlock();
+ }
+ }
+
+ private String printLockStatus(MonitoredEnums lockId) {
+ String s = "";
+
+ for(Long threadid : locks.get(lockId).keySet()) {
+ for(MonitoredEnums lockid : threadTracker.get(threadid)) {
+ s += (" " + lockid.name());
+ }
+
+ s += " |\r\n";
+ }
+
+ return s;
+ }
+
+ public void registerThreadTrackerTask() {
+ threadTrackerSchedule = TimerManager.getInstance().register(new Runnable() {
+ @Override
+ public void run() {
+ accessThreadTracker(true, false, MonitoredEnums.UNDEFINED, -1);
+ }
+ }, 10000, 10000);
+ }
+
+ public void cancelThreadTrackerTask() {
+ threadTrackerSchedule.cancel(false);
+ }
+
+ public static ThreadTracker getInstance() {
+ if (instance == null) {
+ instance = new ThreadTracker();
+ }
+ return instance;
+ }
+}
diff --git a/src/net/server/channel/Channel.java b/src/net/server/channel/Channel.java
index 859a32c107..e2cad9d0e2 100644
--- a/src/net/server/channel/Channel.java
+++ b/src/net/server/channel/Channel.java
@@ -33,7 +33,8 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
+import tools.locks.MonitoredReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
@@ -67,6 +68,7 @@ import tools.MaplePacketCreator;
import client.MapleCharacter;
import constants.ServerConstants;
import server.maps.MapleMiniDungeonInfo;
+import tools.locks.MonitoredEnums;
public final class Channel {
@@ -90,11 +92,11 @@ public final class Channel {
private Map dojoParty = new HashMap<>();
private Map dungeons = new HashMap<>();
- private ReentrantReadWriteLock merchantLock = new ReentrantReadWriteLock(true);
+ private ReentrantReadWriteLock merchantLock = new MonitoredReentrantReadWriteLock(MonitoredEnums.MERCHANT, true);
private ReadLock merchRlock = merchantLock.readLock();
private WriteLock merchWlock = merchantLock.writeLock();
- private Lock lock = new ReentrantLock(true);
+ private Lock lock = new MonitoredReentrantLock(MonitoredEnums.CHANNEL, true);
public Channel(final int world, final int channel) {
this.world = world;
diff --git a/src/net/server/channel/handlers/ReportHandler.java b/src/net/server/channel/handlers/ReportHandler.java
index f45bc24e3f..4493b06821 100644
--- a/src/net/server/channel/handlers/ReportHandler.java
+++ b/src/net/server/channel/handlers/ReportHandler.java
@@ -84,7 +84,7 @@ public final class ReportHandler extends AbstractMaplePacketHandler {
public void addReport(int reporterid, int victimid, int reason, String description, String chatlog) {
Calendar calendar = Calendar.getInstance();
Timestamp currentTimestamp = new java.sql.Timestamp(calendar.getTime().getTime());
- Connection con = null;
+ Connection con;
try {
con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("INSERT INTO reports (`reporttime`, `reporterid`, `victimid`, `reason`, `chatlog`, `description`) VALUES (?, ?, ?, ?, ?, ?)");
diff --git a/src/net/server/channel/handlers/TakeDamageHandler.java b/src/net/server/channel/handlers/TakeDamageHandler.java
index 9f1e5e9759..5f05e51ced 100644
--- a/src/net/server/channel/handlers/TakeDamageHandler.java
+++ b/src/net/server/channel/handlers/TakeDamageHandler.java
@@ -51,6 +51,7 @@ import server.life.MobAttackInfoFactory;
import server.life.MobSkill;
import server.life.MobSkillFactory;
import server.maps.MapleMap;
+import server.maps.MapleMapObject;
import tools.FilePrinter;
import tools.MaplePacketCreator;
import tools.Randomizer;
@@ -78,9 +79,17 @@ public final class TakeDamageHandler extends AbstractMaplePacketHandler {
oid = slea.readInt();
try {
- attacker = (MapleMonster) map.getMapObject(oid);
- List loseItems;
+ MapleMapObject mmo = map.getMapObject(oid);
+ if(mmo instanceof MapleMonster) {
+ attacker = (MapleMonster) mmo;
+ if(attacker.getId() != monsteridfrom) {
+ attacker = null;
+ }
+ }
+
if (attacker != null) {
+ List loseItems;
+
if (attacker.isBuffed(MonsterStatus.NEUTRALISE)) {
return;
}
@@ -119,10 +128,8 @@ public final class TakeDamageHandler extends AbstractMaplePacketHandler {
} catch(ClassCastException e) {
//this happens due to mob on last map damaging player just before changing maps
- if(ServerConstants.USE_DEBUG) {
- e.printStackTrace();
- FilePrinter.printError(FilePrinter.EXCEPTION_CAUGHT, "Attacker is not a mob-type, rather is a " + map.getMapObject(oid).getClass().getName() + " entity.");
- }
+ e.printStackTrace();
+ FilePrinter.printError(FilePrinter.EXCEPTION_CAUGHT, "Attacker is not a mob-type, rather is a " + map.getMapObject(oid).getClass().getName() + " entity.");
return;
}
diff --git a/src/net/server/guild/MapleGuild.java b/src/net/server/guild/MapleGuild.java
index 32b8bbd6f9..9af59bc7f8 100644
--- a/src/net/server/guild/MapleGuild.java
+++ b/src/net/server/guild/MapleGuild.java
@@ -36,12 +36,13 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import net.server.Server;
import net.server.channel.Channel;
import tools.DatabaseConnection;
import tools.MaplePacketCreator;
+import tools.locks.MonitoredEnums;
public class MapleGuild {
public final static int CREATE_GUILD_COST = 1500000;
@@ -52,7 +53,7 @@ public class MapleGuild {
}
private final List members;
- private final Lock membersLock = new ReentrantLock(true);
+ private final Lock membersLock = new MonitoredReentrantLock(MonitoredEnums.GUILD, true);
private String rankTitles[] = new String[5]; // 1 = master, 2 = jr, 5 = lowest member
private String name, notice;
diff --git a/src/net/server/world/MapleParty.java b/src/net/server/world/MapleParty.java
index 7af35b31dc..4aa24d2ec0 100644
--- a/src/net/server/world/MapleParty.java
+++ b/src/net/server/world/MapleParty.java
@@ -30,8 +30,9 @@ import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Map;
import java.util.Comparator;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import java.util.concurrent.locks.Lock;
+import tools.locks.MonitoredEnums;
public class MapleParty {
private int id;
@@ -43,7 +44,7 @@ public class MapleParty {
private Map histMembers = new HashMap<>();
private int nextEntry = 0;
- private Lock lock = new ReentrantLock(true);
+ private Lock lock = new MonitoredReentrantLock(MonitoredEnums.PARTY, true);
public MapleParty(int id, MaplePartyCharacter chrfor) {
this.leaderId = chrfor.getId();
diff --git a/src/net/server/world/World.java b/src/net/server/world/World.java
index bd809eb63f..9b38d60c04 100644
--- a/src/net/server/world/World.java
+++ b/src/net/server/world/World.java
@@ -43,7 +43,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import java.util.Set;
import java.util.HashSet;
import java.util.concurrent.ScheduledFuture;
@@ -68,6 +68,7 @@ import server.maps.AbstractMapleMapObject;
import tools.DatabaseConnection;
import tools.MaplePacketCreator;
import tools.Pair;
+import tools.locks.MonitoredEnums;
/**
*
@@ -89,25 +90,25 @@ public class World {
private Map parties = new HashMap<>();
private AtomicInteger runningPartyId = new AtomicInteger();
- private Lock partyLock = new ReentrantLock(true);
+ private Lock partyLock = new MonitoredReentrantLock(MonitoredEnums.WORLD_PARTY, true);
private Map owlSearched = new LinkedHashMap<>();
- private Lock owlLock = new ReentrantLock();
+ private Lock owlLock = new MonitoredReentrantLock(MonitoredEnums.WORLD_OWL);
- private Lock activePetsLock = new ReentrantLock(true);
+ private Lock activePetsLock = new MonitoredReentrantLock(MonitoredEnums.WORLD_PETS, true);
private Map activePets = new LinkedHashMap<>();
private ScheduledFuture> petsSchedule;
private long petUpdate;
- private Lock activeMountsLock = new ReentrantLock(true);
+ private Lock activeMountsLock = new MonitoredReentrantLock(MonitoredEnums.WORLD_MOUNTS, true);
private Map activeMounts = new LinkedHashMap<>();
private ScheduledFuture> mountsSchedule;
private long mountUpdate;
- private Lock activePlayerShopsLock = new ReentrantLock(true);
+ private Lock activePlayerShopsLock = new MonitoredReentrantLock(MonitoredEnums.WORLD_PSHOPS, true);
private Map activePlayerShops = new LinkedHashMap<>();
- private Lock activeMerchantsLock = new ReentrantLock(true);
+ private Lock activeMerchantsLock = new MonitoredReentrantLock(MonitoredEnums.WORLD_MERCHS, true);
private Map> activeMerchants = new LinkedHashMap<>();
private long merchantUpdate;
diff --git a/src/scripting/event/EventInstanceManager.java b/src/scripting/event/EventInstanceManager.java
index 32b7992f6a..c01bb7f3c3 100644
--- a/src/scripting/event/EventInstanceManager.java
+++ b/src/scripting/event/EventInstanceManager.java
@@ -35,7 +35,8 @@ import java.util.Set;
import java.util.Iterator;
import java.util.Properties;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
+import tools.locks.MonitoredReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
@@ -69,6 +70,7 @@ import server.MapleItemInformationProvider;
import server.life.MapleLifeFactory;
import server.life.MapleNPC;
import tools.MaplePacketCreator;
+import tools.locks.MonitoredEnums;
/**
*
@@ -90,12 +92,12 @@ public class EventInstanceManager {
private List mapIds = new LinkedList<>();
private List isInstanced = new LinkedList<>();
- private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
+ private final ReentrantReadWriteLock lock = new MonitoredReentrantReadWriteLock(MonitoredEnums.EIM, true);
private final ReadLock rL = lock.readLock();
private final WriteLock wL = lock.writeLock();
- private final Lock pL = new ReentrantLock(true);
- private final Lock sL = new ReentrantLock();
+ private final Lock pL = new MonitoredReentrantLock(MonitoredEnums.EIM_PARTY, true);
+ private final Lock sL = new MonitoredReentrantLock(MonitoredEnums.EIM_SCRIPT, true);
private ScheduledFuture> event_schedule = null;
private boolean disposed = false;
diff --git a/src/scripting/event/EventManager.java b/src/scripting/event/EventManager.java
index cb38e3de74..060482e8c8 100644
--- a/src/scripting/event/EventManager.java
+++ b/src/scripting/event/EventManager.java
@@ -53,7 +53,8 @@ import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredEnums;
+import tools.locks.MonitoredReentrantLock;
/**
*
@@ -74,8 +75,8 @@ public class EventManager {
private Integer readyId = 0;
private Properties props = new Properties();
private String name;
- private Lock lobbyLock = new ReentrantLock();
- private Lock queueLock = new ReentrantLock();
+ private Lock lobbyLock = new MonitoredReentrantLock(MonitoredEnums.EM_LOBBY);
+ private Lock queueLock = new MonitoredReentrantLock(MonitoredEnums.EM_QUEUE);
private static final int maxLobbys = 8; // an event manager holds up to this amount of concurrent lobbys
diff --git a/src/server/CashShop.java b/src/server/CashShop.java
index dd9e279bbb..5417ffb4f4 100644
--- a/src/server/CashShop.java
+++ b/src/server/CashShop.java
@@ -31,7 +31,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import provider.MapleData;
import provider.MapleDataProvider;
@@ -46,6 +46,7 @@ import client.inventory.MapleInventoryType;
import client.inventory.MaplePet;
import constants.ItemConstants;
import java.util.Collections;
+import tools.locks.MonitoredEnums;
/*
* @author Flav
@@ -241,7 +242,7 @@ public class CashShop {
private List
- inventory = new ArrayList<>();
private List wishList = new ArrayList<>();
private int notes = 0;
- private Lock lock = new ReentrantLock();
+ private Lock lock = new MonitoredReentrantLock(MonitoredEnums.CASHSHOP);
public CashShop(int accountId, int characterId, int jobType) throws SQLException {
this.accountId = accountId;
diff --git a/src/server/MaplePlayerShop.java b/src/server/MaplePlayerShop.java
index 8a401f3184..2828c43ef0 100644
--- a/src/server/MaplePlayerShop.java
+++ b/src/server/MaplePlayerShop.java
@@ -32,13 +32,14 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import net.SendOpcode;
import server.maps.AbstractMapleMapObject;
import server.maps.MapleMapObjectType;
import tools.MaplePacketCreator;
import tools.Pair;
import tools.data.output.MaplePacketLittleEndianWriter;
+import tools.locks.MonitoredEnums;
/**
*
@@ -55,7 +56,7 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
private List bannedList = new ArrayList<>();
private List> chatLog = new LinkedList<>();
private Map chatSlot = new LinkedHashMap<>();
- private Lock visitorLock = new ReentrantLock(true);
+ private Lock visitorLock = new MonitoredReentrantLock(MonitoredEnums.VISITOR_PSHOP, true);
public MaplePlayerShop(MapleCharacter owner, String description) {
this.setPosition(owner.getPosition());
diff --git a/src/server/MapleStorage.java b/src/server/MapleStorage.java
index 78f73049a0..a082ab124e 100644
--- a/src/server/MapleStorage.java
+++ b/src/server/MapleStorage.java
@@ -34,10 +34,11 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import tools.DatabaseConnection;
import tools.MaplePacketCreator;
import tools.Pair;
+import tools.locks.MonitoredEnums;
/**
*
@@ -50,7 +51,7 @@ public class MapleStorage {
private byte slots;
private Map> typeItems = new HashMap<>();
private List
- items;
- private Lock lock = new ReentrantLock(true);
+ private Lock lock = new MonitoredReentrantLock(MonitoredEnums.STORAGE, true);
private MapleStorage(int id, byte slots, int meso) {
this.id = id;
diff --git a/src/server/life/MapleMonster.java b/src/server/life/MapleMonster.java
index bc86e8e430..7de4519faf 100644
--- a/src/server/life/MapleMonster.java
+++ b/src/server/life/MapleMonster.java
@@ -51,7 +51,8 @@ import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.Lock;
+import tools.locks.MonitoredReentrantLock;
import net.server.world.MapleParty;
import net.server.world.MaplePartyCharacter;
import server.TimerManager;
@@ -62,6 +63,7 @@ import server.maps.MapleMapObjectType;
import tools.MaplePacketCreator;
import tools.Pair;
import tools.Randomizer;
+import tools.locks.MonitoredEnums;
public class MapleMonster extends AbstractLoadedMapleLife {
private ChangeableStats ostats = null; //unused, v83 WZs offers no support for changeable stats.
@@ -84,9 +86,9 @@ public class MapleMonster extends AbstractLoadedMapleLife {
private int team;
private final HashMap takenDamage = new HashMap<>();
- private ReentrantLock externalLock = new ReentrantLock();
- private ReentrantLock monsterLock = new ReentrantLock(true);
- private ReentrantLock statiLock = new ReentrantLock();
+ private Lock externalLock = new MonitoredReentrantLock(MonitoredEnums.MOB_EXT);
+ private Lock monsterLock = new MonitoredReentrantLock(MonitoredEnums.MOB, true);
+ private Lock statiLock = new MonitoredReentrantLock(MonitoredEnums.MOB_STATI);
public MapleMonster(int id, MapleMonsterStats stats) {
super(id);
diff --git a/src/server/life/MobSkillFactory.java b/src/server/life/MobSkillFactory.java
index 17176608dd..8874b4e95f 100644
--- a/src/server/life/MobSkillFactory.java
+++ b/src/server/life/MobSkillFactory.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import tools.locks.MonitoredReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
@@ -34,6 +35,7 @@ import provider.MapleData;
import provider.MapleDataProvider;
import provider.MapleDataProviderFactory;
import provider.MapleDataTool;
+import tools.locks.MonitoredEnums;
/**
*
@@ -44,7 +46,7 @@ public class MobSkillFactory {
private static Map mobSkills = new HashMap();
private final static MapleDataProvider dataSource = MapleDataProviderFactory.getDataProvider(new File(System.getProperty("wzpath") + "/Skill.wz"));
private static MapleData skillRoot = dataSource.getData("MobSkill.img");
- private final static ReentrantReadWriteLock dataLock = new ReentrantReadWriteLock();
+ private final static ReentrantReadWriteLock dataLock = new MonitoredReentrantReadWriteLock(MonitoredEnums.MOBSKILL_FACTORY);
private final static ReadLock rL = dataLock.readLock();
private final static WriteLock wL = dataLock.writeLock();
diff --git a/src/server/maps/MapleGenericPortal.java b/src/server/maps/MapleGenericPortal.java
index 28e40640a3..3b454f6819 100644
--- a/src/server/maps/MapleGenericPortal.java
+++ b/src/server/maps/MapleGenericPortal.java
@@ -27,7 +27,8 @@ import scripting.portal.PortalScriptManager;
import server.MaplePortal;
import tools.MaplePacketCreator;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredEnums;
+import tools.locks.MonitoredReentrantLock;
public class MapleGenericPortal implements MaplePortal {
@@ -117,7 +118,7 @@ public class MapleGenericPortal implements MaplePortal {
if(scriptName != null) {
if(scriptLock == null) {
- scriptLock = new ReentrantLock(false);
+ scriptLock = new MonitoredReentrantLock(MonitoredEnums.PORTAL, false);
}
} else {
scriptLock = null;
diff --git a/src/server/maps/MapleHiredMerchant.java b/src/server/maps/MapleHiredMerchant.java
index 44c265b1eb..2bcd3a7528 100644
--- a/src/server/maps/MapleHiredMerchant.java
+++ b/src/server/maps/MapleHiredMerchant.java
@@ -37,7 +37,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import net.server.Server;
import server.MapleInventoryManipulator;
import server.MapleItemInformationProvider;
@@ -45,6 +45,7 @@ import server.MaplePlayerShopItem;
import tools.DatabaseConnection;
import tools.MaplePacketCreator;
import tools.Pair;
+import tools.locks.MonitoredEnums;
/**
*
@@ -63,7 +64,7 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
private List sold = new LinkedList<>();
private AtomicBoolean open = new AtomicBoolean();
private MapleMap map;
- private Lock visitorLock = new ReentrantLock(true);
+ private Lock visitorLock = new MonitoredReentrantLock(MonitoredEnums.VISITOR_MERCH, true);
public MapleHiredMerchant(final MapleCharacter owner, int itemId, String desc) {
this.setPosition(owner.getPosition());
diff --git a/src/server/maps/MapleMap.java b/src/server/maps/MapleMap.java
index 9a8cb474cf..92c9456474 100644
--- a/src/server/maps/MapleMap.java
+++ b/src/server/maps/MapleMap.java
@@ -52,6 +52,7 @@ import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
+import tools.locks.MonitoredReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
@@ -78,13 +79,14 @@ import server.life.MonsterGlobalDropEntry;
import server.life.SpawnPoint;
import server.partyquest.MonsterCarnival;
import server.partyquest.MonsterCarnivalParty;
-import server.partyquest.Pyramid;
+//import server.partyquest.Pyramid;
import scripting.event.EventInstanceManager;
import server.life.MonsterListener;
import tools.FilePrinter;
import tools.MaplePacketCreator;
import tools.Pair;
import tools.Randomizer;
+import tools.locks.MonitoredEnums;
public class MapleMap {
private static final List rangedMapobjectTypes = Arrays.asList(MapleMapObjectType.SHOP, MapleMapObjectType.ITEM, MapleMapObjectType.NPC, MapleMapObjectType.MONSTER, MapleMapObjectType.DOOR, MapleMapObjectType.SUMMON, MapleMapObjectType.REACTOR);
@@ -161,11 +163,11 @@ public class MapleMap {
if (this.monsterRate == 0) {
this.monsterRate = 1;
}
- final ReentrantReadWriteLock chrLock = new ReentrantReadWriteLock(true);
+ final ReentrantReadWriteLock chrLock = new MonitoredReentrantReadWriteLock(MonitoredEnums.MAP_CHRS, true);
chrRLock = chrLock.readLock();
chrWLock = chrLock.writeLock();
- final ReentrantReadWriteLock objectLock = new ReentrantReadWriteLock(true);
+ final ReentrantReadWriteLock objectLock = new MonitoredReentrantReadWriteLock(MonitoredEnums.MAP_OBJS, true);
objectRLock = objectLock.readLock();
objectWLock = objectLock.writeLock();
}
diff --git a/src/server/maps/MapleMapFactory.java b/src/server/maps/MapleMapFactory.java
index 556794dca9..f692af1096 100644
--- a/src/server/maps/MapleMapFactory.java
+++ b/src/server/maps/MapleMapFactory.java
@@ -33,6 +33,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Collection;
+import tools.locks.MonitoredReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
@@ -46,6 +47,7 @@ import server.life.MapleMonster;
import scripting.event.EventInstanceManager;
import tools.DatabaseConnection;
import tools.StringUtil;
+import tools.locks.MonitoredEnums;
public class MapleMapFactory {
@@ -64,7 +66,7 @@ public class MapleMapFactory {
this.channel = channel;
this.event = eim;
- ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock();
+ ReentrantReadWriteLock rrwl = new MonitoredReentrantReadWriteLock(MonitoredEnums.MAP_FACTORY);
this.mapsRLock = rrwl.readLock();
this.mapsWLock = rrwl.writeLock();
}
diff --git a/src/server/maps/MapleMapItem.java b/src/server/maps/MapleMapItem.java
index 68cf6228bc..0a389b7797 100644
--- a/src/server/maps/MapleMapItem.java
+++ b/src/server/maps/MapleMapItem.java
@@ -24,8 +24,10 @@ import client.MapleCharacter;
import client.MapleClient;
import client.inventory.Item;
import java.awt.Point;
-import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.Lock;
+import tools.locks.MonitoredReentrantLock;
import tools.MaplePacketCreator;
+import tools.locks.MonitoredEnums;
public class MapleMapItem extends AbstractMapleMapObject {
@@ -35,7 +37,7 @@ public class MapleMapItem extends AbstractMapleMapObject {
protected byte type;
protected boolean pickedUp = false, playerDrop;
protected long dropTime;
- private ReentrantLock itemLock = new ReentrantLock();
+ private Lock itemLock = new MonitoredReentrantLock(MonitoredEnums.MAP_ITEM);
public MapleMapItem(Item item, Point position, MapleMapObject dropper, MapleCharacter owner, byte type, boolean playerDrop) {
setPosition(position);
diff --git a/src/server/maps/MapleMiniDungeon.java b/src/server/maps/MapleMiniDungeon.java
index 315ad3e09a..8349a9286e 100644
--- a/src/server/maps/MapleMiniDungeon.java
+++ b/src/server/maps/MapleMiniDungeon.java
@@ -25,8 +25,9 @@ import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import tools.MaplePacketCreator;
+import tools.locks.MonitoredEnums;
/**
*
@@ -35,7 +36,7 @@ import tools.MaplePacketCreator;
public class MapleMiniDungeon {
List players = new ArrayList<>();
ScheduledFuture> timeoutTask = null;
- Lock lock = new ReentrantLock(true);
+ Lock lock = new MonitoredReentrantLock(MonitoredEnums.MINIDUNGEON, true);
int baseMap;
long expireTime;
diff --git a/src/server/maps/MapleReactor.java b/src/server/maps/MapleReactor.java
index 6db3be3769..73baba2891 100644
--- a/src/server/maps/MapleReactor.java
+++ b/src/server/maps/MapleReactor.java
@@ -29,12 +29,13 @@ import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import tools.locks.MonitoredReentrantLock;
import scripting.reactor.ReactorScriptManager;
import server.TimerManager;
import tools.MaplePacketCreator;
import tools.Pair;
+import tools.locks.MonitoredEnums;
/**
*
@@ -53,7 +54,7 @@ public class MapleReactor extends AbstractMapleMapObject {
private boolean shouldCollect;
private boolean attackHit;
private ScheduledFuture> timeoutTask = null;
- private Lock reactorLock = new ReentrantLock(true);
+ private Lock reactorLock = new MonitoredReentrantLock(MonitoredEnums.REACTOR, true);
public MapleReactor(MapleReactorStats stats, int rid) {
this.evstate = (byte)0;
diff --git a/src/tools/FilePrinter.java b/src/tools/FilePrinter.java
index 340129765c..df0195d24d 100644
--- a/src/tools/FilePrinter.java
+++ b/src/tools/FilePrinter.java
@@ -44,7 +44,11 @@ public class FilePrinter {
QUEST_UNCODED = "uncodedQuests.txt",
AUTOSAVING_CHARACTER = "saveCharAuto.txt",
SAVING_CHARACTER = "saveChar.txt",
- USED_COMMANDS = "usedCommands.txt";//more to come (maps)
+ USED_COMMANDS = "usedCommands.txt",
+ DEADLOCK_ERROR = "deadlocks.txt",
+ DEADLOCK_STACK = "deadlocks/path.txt",
+ DEADLOCK_LOCKS = "deadlocks/locks.txt",
+ DEADLOCK_STATE = "deadlocks/state.txt";
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); //for file system purposes, it's nice to use yyyy-MM-dd
private static final String FILE_PATH = "logs/" + sdf.format(Calendar.getInstance().getTime()) + "/"; // + sdf.format(Calendar.getInstance().getTime()) + "/"
diff --git a/src/tools/locks/MonitoredEnums.java b/src/tools/locks/MonitoredEnums.java
new file mode 100644
index 0000000000..aaa0b4dc21
--- /dev/null
+++ b/src/tools/locks/MonitoredEnums.java
@@ -0,0 +1,81 @@
+/*
+ * This file is part of the MapleSolaxiaV2 Maple Story Server
+ *
+ * Copyright (C) 2017 RonanLana
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package tools.locks;
+
+/**
+ *
+ * @author RonanLana
+ */
+
+public enum MonitoredEnums {
+ UNDEFINED(-1),
+ CHR(0),
+ EFF(1),
+ PET(2),
+ PRT(3),
+ CLIENT(4),
+ BOOK(5),
+ ITEM(6),
+ INVENTORY(7),
+ SHANDLER_IDLE(8),
+ SHANDLER_TEMP(9),
+ BUFF_STORAGE(10),
+ PLAYER_STORAGE(11),
+ SERVER(12),
+ MERCHANT(13),
+ CHANNEL(14),
+ GUILD(15),
+ PARTY(16),
+ WORLD_PARTY(17),
+ WORLD_OWL(18),
+ WORLD_PETS(19),
+ WORLD_MOUNTS(20),
+ WORLD_PSHOPS(21),
+ WORLD_MERCHS(21),
+ EIM(22),
+ EIM_PARTY(23),
+ EIM_SCRIPT(24),
+ EM_LOBBY(25),
+ EM_QUEUE(26),
+ CASHSHOP(27),
+ VISITOR_PSHOP(28),
+ STORAGE(29),
+ MOB_EXT(30),
+ MOB(31),
+ MOB_STATI(32),
+ MOBSKILL_FACTORY(33),
+ PORTAL(34),
+ VISITOR_MERCH(35),
+ MAP_CHRS(36),
+ MAP_OBJS(37),
+ MAP_FACTORY(38),
+ MAP_ITEM(39),
+ MINIDUNGEON(40),
+ REACTOR(41);
+
+ private final int i;
+
+ private MonitoredEnums(int val) {
+ this.i = val;
+ }
+
+ public int getValue() {
+ return i;
+ }
+}
diff --git a/src/tools/locks/MonitoredReadLock.java b/src/tools/locks/MonitoredReadLock.java
new file mode 100644
index 0000000000..7c5c1be516
--- /dev/null
+++ b/src/tools/locks/MonitoredReadLock.java
@@ -0,0 +1,147 @@
+/*
+ * This file is part of the MapleSolaxiaV2 Maple Story Server
+ *
+ * Copyright (C) 2017 RonanLana
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package tools.locks;
+
+import constants.ServerConstants;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.ScheduledFuture;
+import server.TimerManager;
+import net.server.Server;
+import net.server.audit.ThreadTracker;
+
+import tools.FilePrinter;
+
+/**
+ *
+ * @author RonanLana
+ */
+public class MonitoredReadLock extends ReentrantReadWriteLock.ReadLock {
+ private ScheduledFuture> timeoutSchedule = null;
+ private StackTraceElement[] deadlockedState = null;
+ private final MonitoredEnums id;
+ private final int hashcode;
+ private final Lock state = new ReentrantLock(true);
+ private final AtomicInteger reentrantCount = new AtomicInteger(0);
+
+ public MonitoredReadLock(MonitoredReentrantReadWriteLock lock) {
+ super(lock);
+ this.id = lock.id;
+ hashcode = this.hashCode();
+ }
+
+ @Override
+ public void lock() {
+ super.lock();
+
+ if(ServerConstants.USE_THREAD_TRACKER) {
+ if(deadlockedState != null) {
+ DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
+ dateFormat.setTimeZone(TimeZone.getTimeZone(ServerConstants.TIMEZONE));
+
+ //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) + "\r\n\r\n");
+ ThreadTracker.getInstance().accessThreadTracker(true, true, id, hashcode);
+ deadlockedState = null;
+ }
+
+ registerLocking();
+ }
+ }
+
+ @Override
+ public void unlock() {
+ if(ServerConstants.USE_THREAD_TRACKER) {
+ unregisterLocking();
+ }
+ super.unlock();
+ }
+
+ @Override
+ public boolean tryLock() {
+ if(super.tryLock()) {
+ if(ServerConstants.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) + "\r\n\r\n");
+ 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(new Runnable() {
+ @Override
+ public void run() {
+ issueDeadlock(t);
+ }
+ }, ServerConstants.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(int i = 0; i < list.length; i++) {
+ s += (" " + list[i].toString() + "\r\n");
+ }
+
+ return s;
+ }
+}
diff --git a/src/tools/locks/MonitoredReentrantLock.java b/src/tools/locks/MonitoredReentrantLock.java
new file mode 100644
index 0000000000..6934b92ae5
--- /dev/null
+++ b/src/tools/locks/MonitoredReentrantLock.java
@@ -0,0 +1,150 @@
+/*
+ * This file is part of the MapleSolaxiaV2 Maple Story Server
+ *
+ * Copyright (C) 2017 RonanLana
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package tools.locks;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.ScheduledFuture;
+import constants.ServerConstants;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+import server.TimerManager;
+import net.server.Server;
+import net.server.audit.ThreadTracker;
+import tools.FilePrinter;
+
+/**
+ *
+ * @author RonanLana
+ */
+public class MonitoredReentrantLock extends ReentrantLock {
+ private ScheduledFuture> timeoutSchedule = null;
+ private StackTraceElement[] deadlockedState = null;
+ private final MonitoredEnums id;
+ private final int hashcode;
+ private final Lock state = new ReentrantLock(true);
+ private final AtomicInteger reentrantCount = new AtomicInteger(0);
+
+ public MonitoredReentrantLock(MonitoredEnums id) {
+ super();
+ this.id = id;
+ hashcode = this.hashCode();
+ }
+
+ public MonitoredReentrantLock(MonitoredEnums id, boolean fair) {
+ super(fair);
+ this.id = id;
+ hashcode = this.hashCode();
+ }
+
+ @Override
+ public void lock() {
+ super.lock();
+ if(ServerConstants.USE_THREAD_TRACKER) {
+ if(deadlockedState != null) {
+ DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
+ dateFormat.setTimeZone(TimeZone.getTimeZone(ServerConstants.TIMEZONE));
+
+ //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) + "\r\n\r\n");
+ ThreadTracker.getInstance().accessThreadTracker(true, true, id, hashcode);
+ deadlockedState = null;
+ }
+
+ registerLocking();
+ }
+ }
+
+ @Override
+ public void unlock() {
+ if(ServerConstants.USE_THREAD_TRACKER) {
+ unregisterLocking();
+ }
+ super.unlock();
+ }
+
+ @Override
+ public boolean tryLock() {
+ if(super.tryLock()) {
+ if(ServerConstants.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) + "\r\n\r\n");
+ 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(new Runnable() {
+ @Override
+ public void run() {
+ issueDeadlock(t);
+ }
+ }, ServerConstants.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(int i = 0; i < list.length; i++) {
+ s += (" " + list[i].toString() + "\r\n");
+ }
+
+ return s;
+ }
+}
diff --git a/src/tools/locks/MonitoredReentrantReadWriteLock.java b/src/tools/locks/MonitoredReentrantReadWriteLock.java
new file mode 100644
index 0000000000..30203327ae
--- /dev/null
+++ b/src/tools/locks/MonitoredReentrantReadWriteLock.java
@@ -0,0 +1,49 @@
+/*
+ * This file is part of the MapleSolaxiaV2 Maple Story Server
+ *
+ * Copyright (C) 2017 RonanLana
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package tools.locks;
+
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ *
+ * @author RonanLana
+ */
+public class MonitoredReentrantReadWriteLock extends ReentrantReadWriteLock {
+ public final MonitoredEnums id;
+
+ public MonitoredReentrantReadWriteLock(MonitoredEnums id) {
+ super();
+ this.id = id;
+ }
+
+ public MonitoredReentrantReadWriteLock(MonitoredEnums id, boolean fair) {
+ super(fair);
+ this.id = id;
+ }
+
+ @Override
+ public ReadLock readLock() {
+ return super.readLock();
+ }
+
+ @Override
+ public WriteLock writeLock() {
+ return super.writeLock();
+ }
+}
diff --git a/src/tools/locks/MonitoredWriteLock.java b/src/tools/locks/MonitoredWriteLock.java
new file mode 100644
index 0000000000..5c9344ce9e
--- /dev/null
+++ b/src/tools/locks/MonitoredWriteLock.java
@@ -0,0 +1,145 @@
+/*
+ * This file is part of the MapleSolaxiaV2 Maple Story Server
+ *
+ * Copyright (C) 2017 RonanLana
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package tools.locks;
+
+import constants.ServerConstants;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.ScheduledFuture;
+import server.TimerManager;
+import net.server.Server;
+import net.server.audit.ThreadTracker;
+import tools.FilePrinter;
+
+/**
+ *
+ * @author RonanLana
+ */
+public class MonitoredWriteLock extends ReentrantReadWriteLock.WriteLock {
+ private ScheduledFuture> timeoutSchedule = null;
+ private StackTraceElement[] deadlockedState = null;
+ private final MonitoredEnums id;
+ private final int hashcode;
+ private final Lock state = new ReentrantLock(true);
+ private final AtomicInteger reentrantCount = new AtomicInteger(0);
+
+ public MonitoredWriteLock(MonitoredReentrantReadWriteLock lock) {
+ super(lock);
+ this.id = lock.id;
+ hashcode = this.hashCode();
+ }
+
+ @Override
+ public void lock() {
+ super.lock();
+ if(ServerConstants.USE_THREAD_TRACKER) {
+ if(deadlockedState != null) {
+ DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
+ dateFormat.setTimeZone(TimeZone.getTimeZone(ServerConstants.TIMEZONE));
+
+ //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) + "\r\n\r\n");
+ ThreadTracker.getInstance().accessThreadTracker(true, true, id, hashcode);
+ deadlockedState = null;
+ }
+
+ registerLocking();
+ }
+ }
+
+ @Override
+ public void unlock() {
+ if(ServerConstants.USE_THREAD_TRACKER) {
+ unregisterLocking();
+ }
+ super.unlock();
+ }
+
+ @Override
+ public boolean tryLock() {
+ if(super.tryLock()) {
+ if(ServerConstants.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) + "\r\n\r\n");
+ 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(new Runnable() {
+ @Override
+ public void run() {
+ issueDeadlock(t);
+ }
+ }, ServerConstants.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(int i = 0; i < list.length; i++) {
+ s += (" " + list[i].toString() + "\r\n");
+ }
+
+ return s;
+ }
+}
diff --git a/tools/MapleIdRetriever/dist/MapleIdRetriever.jar b/tools/MapleIdRetriever/dist/MapleIdRetriever.jar
index 52739d2da9..dc790fed4b 100644
Binary files a/tools/MapleIdRetriever/dist/MapleIdRetriever.jar and b/tools/MapleIdRetriever/dist/MapleIdRetriever.jar differ
diff --git a/tools/MapleIdRetriever/lib/fetch.txt b/tools/MapleIdRetriever/lib/fetch.txt
index 598b0863a2..ff68f13fc0 100644
--- a/tools/MapleIdRetriever/lib/fetch.txt
+++ b/tools/MapleIdRetriever/lib/fetch.txt
@@ -1,14 +1,24 @@
-Red Potion
-Lemon
-W. Ramen
-Elixir
-Mana Elixir
-Mushroom Miso Ramen
-Power Elixir
-Lunar Gloves
-LeFay Jester
-Eclipse Earrings
-Herculean Crown
-Lockewood Hat
-Pickpocket Pilfer
-Eclipse Cloak
\ No newline at end of file
+Gladius
+Niam
+Titan
+Crescent Polearm
+Iron Dagger
+White Crusader Chainmail (Male)
+Wizard Wand
+Wizard Staff
+Golden Pride
+Blue Guiltian
+Red Amorian Skirt (Female)
+Red Salt Shoes
+Green Pole-Feather Hat
+Black Bennis Chainmail (Male)
+Red Legolier (Male)
+Blue Legolier Pants (Male)
+Green Marker
+Gold Brace
+Green Hunter Boots
+Dragon Toenail
+Rouge Way
+Guardian Katara
+Justice Katara
+Norman Grip
diff --git a/tools/MapleIdRetriever/lib/result.txt b/tools/MapleIdRetriever/lib/result.txt
index e69de29bb2..766d1fdfef 100644
--- a/tools/MapleIdRetriever/lib/result.txt
+++ b/tools/MapleIdRetriever/lib/result.txt
@@ -0,0 +1,20 @@
+1302008
+1412004
+1422007
+1442009
+1332010
+1372001
+1382002
+1002013
+1002152
+1061047
+1072090
+1002137
+1040023
+1040072
+1060062
+1082049
+1082072
+1072081
+1332031
+1482003
diff --git a/tools/MapleIdRetriever/nbproject/private/private.properties b/tools/MapleIdRetriever/nbproject/private/private.properties
index 1c5af62efc..646b670577 100644
--- a/tools/MapleIdRetriever/nbproject/private/private.properties
+++ b/tools/MapleIdRetriever/nbproject/private/private.properties
@@ -1,2 +1,2 @@
compile.on.save=true
-user.properties.file=C:\\Users\\USER\\AppData\\Roaming\\NetBeans\\8.0.2\\build.properties
+user.properties.file=C:\\Users\\RonanLana\\AppData\\Roaming\\NetBeans\\8.0.2\\build.properties
diff --git a/tools/MapleIdRetriever/src/mapleidretriever/MapleIdRetriever.java b/tools/MapleIdRetriever/src/mapleidretriever/MapleIdRetriever.java
index 4d6bd57223..64b37e93ca 100644
--- a/tools/MapleIdRetriever/src/mapleidretriever/MapleIdRetriever.java
+++ b/tools/MapleIdRetriever/src/mapleidretriever/MapleIdRetriever.java
@@ -41,9 +41,11 @@ import java.util.ArrayList;
* Set whether you are first installing the handbook on the SQL Server (TRUE) or just fetching whatever is on your "fetch.txt"
* file (FALSE) on the INSTALL_SQLTABLE property and build the project. With all done, run the Java executable.
*
+ * Expected installing time: 30 minutes
+ *
*/
public class MapleIdRetriever {
- private final static boolean INSTALL_SQLTABLE = true;
+ private final static boolean INSTALL_SQLTABLE = false;
static String host = "jdbc:mysql://localhost:3306/maplesolaxia";
static String driver = "com.mysql.jdbc.Driver";
@@ -83,7 +85,12 @@ public class MapleIdRetriever {
if(tokens.length > 1) {
PreparedStatement ps = con.prepareStatement("INSERT INTO `handbook` (`id`, `name`) VALUES (?, ?)");
- ps.setInt(1, Integer.parseInt(tokens[0]));
+ try {
+ ps.setInt(1, Integer.parseInt(tokens[0]));
+ } catch (NumberFormatException npe) { // odd...
+ String num = tokens[0].substring(1);
+ ps.setInt(1, Integer.parseInt(num));
+ }
ps.setString(2, tokens[1]);
ps.execute();
}