diff --git a/src/main/java/net/server/coordinator/session/InitializationResult.java b/src/main/java/net/server/coordinator/session/InitializationResult.java new file mode 100644 index 0000000000..dc18ef502c --- /dev/null +++ b/src/main/java/net/server/coordinator/session/InitializationResult.java @@ -0,0 +1,20 @@ +package net.server.coordinator.session; + +import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult; + +enum InitializationResult { + SUCCESS(AntiMulticlientResult.SUCCESS), + ALREADY_INITIALIZED(AntiMulticlientResult.REMOTE_PROCESSING), + TIMED_OUT(AntiMulticlientResult.COORDINATOR_ERROR), + ERROR(AntiMulticlientResult.COORDINATOR_ERROR); + + private final AntiMulticlientResult antiMulticlientResult; + + InitializationResult(AntiMulticlientResult antiMulticlientResult) { + this.antiMulticlientResult = antiMulticlientResult; + } + + public AntiMulticlientResult getAntiMulticlientResult() { + return antiMulticlientResult; + } +} diff --git a/src/main/java/net/server/coordinator/session/MapleSessionCoordinator.java b/src/main/java/net/server/coordinator/session/MapleSessionCoordinator.java index 0d9042b0ea..07fca506a0 100644 --- a/src/main/java/net/server/coordinator/session/MapleSessionCoordinator.java +++ b/src/main/java/net/server/coordinator/session/MapleSessionCoordinator.java @@ -23,8 +23,6 @@ import client.MapleCharacter; import client.MapleClient; import config.YamlConfig; import net.server.Server; -import net.server.audit.locks.MonitoredLockType; -import net.server.audit.locks.factory.MonitoredReentrantLockFactory; import net.server.coordinator.login.LoginStorage; import org.apache.mina.core.session.IoSession; import org.slf4j.Logger; @@ -38,8 +36,6 @@ import java.util.*; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; /** @@ -63,7 +59,8 @@ public class MapleSessionCoordinator { MANY_ACCOUNT_ATTEMPTS, COORDINATOR_ERROR } - + + private final SessionInitialization sessionInit = new SessionInitialization(); private final LoginStorage loginStorage = new LoginStorage(); private final Map onlineClients = new HashMap<>(); private final Set onlineRemoteHwids = new HashSet<>(); @@ -72,12 +69,8 @@ public class MapleSessionCoordinator { private final ConcurrentHashMap cachedHostHwids = new ConcurrentHashMap<>(); private final ConcurrentHashMap cachedHostTimeout = new ConcurrentHashMap<>(); - private final List poolLock = new ArrayList<>(100); private MapleSessionCoordinator() { - for(int i = 0; i < 100; i++) { - poolLock.add(MonitoredReentrantLockFactory.createLock(MonitoredLockType.SERVER_LOGIN_COORD)); - } } private static boolean attemptAccountAccess(int accountId, String nibbleHwid, boolean routineCheck) { @@ -109,10 +102,6 @@ public class MapleSessionCoordinator { return false; } - - private Lock getCoodinatorLock(String remoteHost) { - return poolLock.get(Math.abs(remoteHost.hashCode()) % 100); - } public static String getSessionRemoteAddress(IoSession session) { return (String) session.getAttribute(MapleClient.CLIENT_REMOTE_ADDRESS); @@ -152,9 +141,8 @@ public class MapleSessionCoordinator { } String remoteHost = getSessionRemoteHost(session); - Lock lock = getCoodinatorLock(remoteHost); - AntiMulticlientResult result = addToRemoteHostPoolWithRetries(lock, remoteHost); - switch (result) { + final InitializationResult initResult = sessionInit.initialize(remoteHost); + switch (initResult.getAntiMulticlientResult()) { case REMOTE_PROCESSING -> { return false; } @@ -181,12 +169,7 @@ public class MapleSessionCoordinator { return true; } finally { - lock.lock(); - try { - pooledRemoteHosts.remove(remoteHost); - } finally { - lock.unlock(); - } + sessionInit.finalize(remoteHost); } } @@ -224,10 +207,9 @@ public class MapleSessionCoordinator { } String remoteHost = getSessionRemoteHost(session); - final Lock lock = getCoodinatorLock(remoteHost); - AntiMulticlientResult hostPoolAdditionResult = addToRemoteHostPoolWithRetries(lock, remoteHost); - if (hostPoolAdditionResult != AntiMulticlientResult.SUCCESS) { - return hostPoolAdditionResult; + InitializationResult initResult = sessionInit.initialize(remoteHost); + if (initResult != InitializationResult.SUCCESS) { + return initResult.getAntiMulticlientResult(); } try { @@ -254,12 +236,7 @@ public class MapleSessionCoordinator { return AntiMulticlientResult.SUCCESS; } finally { - lock.lock(); - try { - pooledRemoteHosts.remove(remoteHost); - } finally { - lock.unlock(); - } + sessionInit.finalize(remoteHost); } } @@ -271,10 +248,9 @@ public class MapleSessionCoordinator { return AntiMulticlientResult.SUCCESS; } - final Lock lock = getCoodinatorLock(remoteHost); - AntiMulticlientResult hostPoolAdditionResult = addToRemoteHostPoolWithRetries(lock, remoteHost); - if (hostPoolAdditionResult != AntiMulticlientResult.SUCCESS) { - return hostPoolAdditionResult; + final InitializationResult initResult = sessionInit.initialize(remoteHost); + if (initResult != InitializationResult.SUCCESS) { + return initResult.getAntiMulticlientResult(); } try { @@ -303,48 +279,10 @@ public class MapleSessionCoordinator { return AntiMulticlientResult.SUCCESS; } finally { - lock.lock(); - try { - pooledRemoteHosts.remove(remoteHost); - } finally { - lock.unlock(); - } + sessionInit.finalize(remoteHost); } } - private AntiMulticlientResult addToRemoteHostPoolWithRetries(Lock lock, String remoteHost) { - try { - int tries = 0; - while (true) { - if (lock.tryLock()) { - try { - if (pooledRemoteHosts.contains(remoteHost)) { - return AntiMulticlientResult.REMOTE_PROCESSING; - } - - pooledRemoteHosts.add(remoteHost); - } finally { - lock.unlock(); - } - - break; - } else { - if(tries == 2) { - return AntiMulticlientResult.COORDINATOR_ERROR; - } - tries++; - - Thread.sleep(1777); - } - } - } catch (Exception e) { - e.printStackTrace(); - return AntiMulticlientResult.COORDINATOR_ERROR; - } - - return AntiMulticlientResult.SUCCESS; - } - private static void associateHwidAccountIfAbsent(String remoteHwid, int accountId) { try (Connection con = DatabaseConnection.getConnection()) { List hwids = SessionDAO.getHwidsForAccount(con, accountId); diff --git a/src/main/java/net/server/coordinator/session/SessionInitialization.java b/src/main/java/net/server/coordinator/session/SessionInitialization.java new file mode 100644 index 0000000000..6e160099de --- /dev/null +++ b/src/main/java/net/server/coordinator/session/SessionInitialization.java @@ -0,0 +1,88 @@ +package net.server.coordinator.session; + +import net.server.audit.locks.MonitoredLockType; +import net.server.audit.locks.factory.MonitoredReentrantLockFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.locks.Lock; + +/** + * Manages session initialization using remote host (ip address). + */ +public class SessionInitialization { + private final static Logger log = LoggerFactory.getLogger(SessionInitialization.class); + private static final int MAX_INIT_TRIES = 2; + private static final long RETRY_DELAY_MILLIS = 1777; + + private final Set remoteHostsInInitState = new HashSet<>(); + private final List locks = new ArrayList<>(100); + + SessionInitialization() { + for (int i = 0; i < 100; i++) { + locks.add(MonitoredReentrantLockFactory.createLock(MonitoredLockType.SERVER_LOGIN_COORD)); + } + } + + private Lock getLock(String remoteHost) { + return locks.get(Math.abs(remoteHost.hashCode()) % 100); + } + + /** + * Try to initialize a session. Should be called before any session initialization procedure. + * + * @return InitializationResult.SUCCESS if initialization was successful. + * If it was successful, finalize() needs to be called shortly after, + * or else the initialization will be left hanging in a bad state, + * which means any subsequent initialization from the same remote host will fail. + */ + public InitializationResult initialize(String remoteHost) { + final Lock lock = getLock(remoteHost); + try { + int tries = 0; + while (true) { + if (lock.tryLock()) { + try { + if (remoteHostsInInitState.contains(remoteHost)) { + return InitializationResult.ALREADY_INITIALIZED; + } + + remoteHostsInInitState.add(remoteHost); + } finally { + lock.unlock(); + } + + break; + } else { + if (tries++ == MAX_INIT_TRIES) { + return InitializationResult.TIMED_OUT; + } + + Thread.sleep(RETRY_DELAY_MILLIS); + } + } + } catch (Exception e) { + log.error("Failed to initialize session.", e); + return InitializationResult.ERROR; + } + + return InitializationResult.SUCCESS; + } + + /** + * Finalize an initialization. Should be called after any session initialization procedure. + */ + public void finalize(String remoteHost) { + final Lock lock = getLock(remoteHost); + lock.lock(); + try { + remoteHostsInInitState.remove(remoteHost); + } finally { + lock.unlock(); + } + } +}