Dynamic World/Channel deployment + Channel scheduler update

Added "8-slot SETUP expand" item on the CashShop.
Solved a concurrency issue on the fameGainByQuest method.
Refactored many resource freeing modules throughout the source code.
Implemented dynamic deployment of worlds and channels on the server system. Only creation of channels and worlds are available on this feature.
Added a dedicated worker for schedules requested on EventManager.
Fixed a potential cause for deadlocks on the channel schedulers' system.
Refactored many schedules used by the EventManager and Channel, futher improving overall scheduling performance on the server.
This commit is contained in:
ronancpl
2018-07-23 20:45:41 -03:00
parent bee8b5259b
commit 8aadf7c369
48 changed files with 1152 additions and 260 deletions

View File

@@ -33,7 +33,6 @@ import java.util.Map;
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.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
@@ -92,6 +91,7 @@ public final class Channel {
private MobClearSkillScheduler mobClearSkillSchedulers[] = new MobClearSkillScheduler[4];
private MobMistScheduler mobMistSchedulers[] = new MobMistScheduler[4];
private FaceExpressionScheduler faceExpressionSchedulers[] = new FaceExpressionScheduler[4];
private EventScheduler eventSchedulers[] = new EventScheduler[4];
private OverallScheduler channelSchedulers[] = new OverallScheduler[4];
private Map<Integer, MapleHiredMerchant> hiredMerchants = new HashMap<>();
private final Map<Integer, Integer> storedVars = new HashMap<>();
@@ -100,6 +100,9 @@ public final class Channel {
private MapleEvent event;
private boolean finishedShutdown = false;
private int usedDojo = 0;
private ScheduledFuture<?> respawnTask;
private int[] dojoStage;
private long[] dojoFinishTime;
private ScheduledFuture<?>[] dojoTask;
@@ -123,9 +126,9 @@ public final class Channel {
private ReadLock merchRlock = merchantLock.readLock();
private WriteLock merchWlock = merchantLock.writeLock();
private Lock faceLock[] = new MonitoredReentrantLock[4];
private MonitoredReentrantLock faceLock[] = new MonitoredReentrantLock[4];
private Lock lock = new MonitoredReentrantLock(MonitoredLockType.CHANNEL, true);
private MonitoredReentrantLock lock = new MonitoredReentrantLock(MonitoredLockType.CHANNEL, true);
public Channel(final int world, final int channel, long startTime) {
this.world = world;
@@ -141,7 +144,7 @@ public final class Channel {
IoBuffer.setUseDirectBuffer(false);
IoBuffer.setAllocator(new SimpleBufferAllocator());
acceptor = new NioSocketAcceptor();
TimerManager.getInstance().register(new respawnMaps(), ServerConstants.RESPAWN_INTERVAL);
respawnTask = TimerManager.getInstance().register(new respawnMaps(), ServerConstants.RESPAWN_INTERVAL);
acceptor.setHandler(new MapleServerHandler(world, channel));
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30);
acceptor.getFilterChain().addLast("codec", (IoFilter) new ProtocolCodecFilter(new MapleCodecFactory()));
@@ -169,6 +172,7 @@ public final class Channel {
mobClearSkillSchedulers[i] = new MobClearSkillScheduler();
mobMistSchedulers[i] = new MobMistScheduler();
faceExpressionSchedulers[i] = new FaceExpressionScheduler(faceLock[i]);
eventSchedulers[i] = new EventScheduler();
channelSchedulers[i] = new OverallScheduler();
}
@@ -191,6 +195,20 @@ public final class Channel {
closeAllMerchants();
players.disconnectAll();
if(respawnTask != null) {
respawnTask.cancel(false);
respawnTask = null;
}
mapFactory.dispose();
mapFactory = null;
eventSM.cancel();
eventSM = null;
closeChannelSchedules();
acceptor.unbind();
finishedShutdown = true;
@@ -200,8 +218,58 @@ public final class Channel {
System.err.println("Error while shutting down Channel " + channel + " on World " + world + "\r\n" + e);
}
}
private void closeChannelSchedules() {
for(int i = 0; i < 20; i++) {
if(dojoTask[i] != null) {
dojoTask[i].cancel(false);
dojoTask[i] = null;
}
}
public void closeAllMerchants() {
for(int i = 0; i < 4; i++) {
if(mobStatusSchedulers[i] != null) {
mobStatusSchedulers[i].dispose();
mobStatusSchedulers[i] = null;
}
if(mobAnimationSchedulers[i] != null) {
mobAnimationSchedulers[i].dispose();
mobAnimationSchedulers[i] = null;
}
if(mobClearSkillSchedulers[i] != null) {
mobClearSkillSchedulers[i].dispose();
mobClearSkillSchedulers[i] = null;
}
if(mobMistSchedulers[i] != null) {
mobMistSchedulers[i].dispose();
mobMistSchedulers[i] = null;
}
if(faceExpressionSchedulers[i] != null) {
faceExpressionSchedulers[i].dispose();
faceExpressionSchedulers[i] = null;
}
if(eventSchedulers[i] != null) {
eventSchedulers[i].dispose();
eventSchedulers[i] = null;
}
if(channelSchedulers[i] != null) {
channelSchedulers[i].dispose();
channelSchedulers[i] = null;
}
faceLock[i].dispose();
}
lock.dispose();
}
private void closeAllMerchants() {
merchWlock.lock();
try {
final Iterator<MapleHiredMerchant> hmit = hiredMerchants.values().iterator();
@@ -442,7 +510,7 @@ public final class Channel {
}
}
private int getDojoSlot(int dojoMapId) {
private static int getDojoSlot(int dojoMapId) {
return (dojoMapId % 100) + ((dojoMapId / 10000 == 92502) ? 5 : 0);
}
@@ -456,7 +524,7 @@ public final class Channel {
resetDojo(dojoMapId, 0);
}
public void resetDojo(int dojoMapId, int thisStg) {
private void resetDojo(int dojoMapId, int thisStg) {
int slot = getDojoSlot(dojoMapId);
this.dojoStage[slot] = thisStg;
@@ -880,6 +948,10 @@ public final class Channel {
mobMistSchedulers[getChannelSchedulerIndex(mapid)].registerMistCancelAction(runAction, delay);
}
public void registerEventAction(int mapid, Runnable runAction, long delay) {
eventSchedulers[getChannelSchedulerIndex(mapid)].registerDelayedAction(runAction, delay);
}
public void registerOverallAction(int mapid, Runnable runAction, long delay) {
channelSchedulers[getChannelSchedulerIndex(mapid)].registerDelayedAction(runAction, delay);
}
@@ -902,13 +974,16 @@ public final class Channel {
faceLock[lockid].lock();
try {
if(chr.isLoggedinWorld()) {
faceExpressionSchedulers[lockid].registerFaceExpression(chr.getId(), cancelAction);
map.broadcastMessage(chr, MaplePacketCreator.facialExpression(chr, emote), false);
if(!chr.isLoggedinWorld()) {
return;
}
faceExpressionSchedulers[lockid].registerFaceExpression(chr.getId(), cancelAction);
} finally {
faceLock[lockid].unlock();
}
map.broadcastMessage(chr, MaplePacketCreator.facialExpression(chr, emote), false);
}
public void unregisterFaceExpression(int mapid, MapleCharacter chr) {

View File

@@ -32,6 +32,8 @@ import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
public final class GiveFameHandler extends AbstractMaplePacketHandler {
@Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
MapleCharacter target = (MapleCharacter) c.getPlayer().getMap().getMapObject(slea.readInt());
int mode = slea.readByte();

View File

@@ -28,10 +28,12 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.Lock;
import server.TimerManager;
import tools.Pair;
import net.server.Server;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReentrantLock;
import server.TimerManager;
import tools.Pair;
/**
*
@@ -40,11 +42,11 @@ import net.server.audit.locks.MonitoredReentrantLock;
public abstract class BaseScheduler {
private int idleProcs = 0;
private List<SchedulerListener> listeners = new LinkedList<>();
private final List<Lock> externalLocks;
private final List<Lock> externalLocks = new LinkedList<>();
private Map<Object, Pair<Runnable, Long>> registeredEntries = new HashMap<>();
private ScheduledFuture<?> schedulerTask = null;
private Lock schedulerLock;
private MonitoredReentrantLock schedulerLock;
private Runnable monitorTask = new Runnable() {
@Override
public void run() {
@@ -54,13 +56,15 @@ public abstract class BaseScheduler {
protected BaseScheduler(MonitoredLockType lockType) {
schedulerLock = new MonitoredReentrantLock(lockType, true);
externalLocks = new LinkedList<>();
}
// 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<Lock> extLocks) {
schedulerLock = new MonitoredReentrantLock(lockType, true);
externalLocks = extLocks;
for(Lock lock : extLocks) {
externalLocks.add(lock);
}
}
protected void addListener(SchedulerListener listener) {
@@ -112,13 +116,13 @@ public abstract class BaseScheduler {
unlockScheduler();
}
long timeNow = System.currentTimeMillis();
long timeNow = Server.getInstance().getCurrentTime();
toRemove = new LinkedList<>();
for(Entry<Object, Pair<Runnable, Long>> rmd : registeredEntriesCopy.entrySet()) {
Pair<Runnable, Long> r = rmd.getValue();
if(r.getRight() < timeNow) {
r.getLeft().run(); // runs the cancel action
r.getLeft().run(); // runs the scheduled action
toRemove.add(rmd.getKey());
}
}
@@ -145,21 +149,29 @@ public abstract class BaseScheduler {
schedulerTask = TimerManager.getInstance().register(monitorTask, ServerConstants.MOB_STATUS_MONITOR_PROC, ServerConstants.MOB_STATUS_MONITOR_PROC);
}
registeredEntries.put(key, new Pair<>(removalAction, System.currentTimeMillis() + duration));
registeredEntries.put(key, new Pair<>(removalAction, Server.getInstance().getCurrentTime() + duration));
} finally {
unlockScheduler();
}
}
protected void interruptEntry(Object key) {
Runnable toRun = null;
lockScheduler();
try {
Pair<Runnable, Long> rm = registeredEntries.remove(key);
if(rm != null) rm.getLeft().run();
if(rm != null) {
toRun = rm.getLeft();
}
} finally {
unlockScheduler();
}
if(toRun != null) {
toRun.run();
}
dispatchRemovedEntries(Collections.singletonList(key), false);
}
@@ -168,4 +180,22 @@ public abstract class BaseScheduler {
listener.removedScheduledEntries(toRemove, fromUpdate);
}
}
public void dispose() {
lockScheduler();
try {
if(schedulerTask != null) {
schedulerTask.cancel(false);
schedulerTask = null;
}
listeners.clear();
externalLocks.clear();
registeredEntries.clear();
} finally {
unlockScheduler();
}
schedulerLock.dispose();
}
}

View File

@@ -0,0 +1,36 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft 2016 - 2018 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.channel.worker;
import net.server.audit.locks.MonitoredLockType;
/**
*
* @author Ronan
*/
public class EventScheduler extends BaseScheduler {
public EventScheduler() {
super(MonitoredLockType.CHANNEL_EVENTS);
}
public void registerDelayedAction(Runnable runAction, long delay) {
registerEntry(runAction, runAction, delay);
}
}

View File

@@ -24,7 +24,6 @@ import net.server.audit.locks.MonitoredLockType;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import net.server.audit.locks.MonitoredReentrantLock;
/**
@@ -33,7 +32,7 @@ import net.server.audit.locks.MonitoredReentrantLock;
*/
public class MobAnimationScheduler extends BaseScheduler {
Set<Integer> onAnimationMobs = new HashSet<>(1000);
private Lock animationLock = new MonitoredReentrantLock(MonitoredLockType.CHANNEL_MOBANIMAT, true);
private MonitoredReentrantLock animationLock = new MonitoredReentrantLock(MonitoredLockType.CHANNEL_MOBANIMAT, true);
private static Runnable r = new Runnable() {
@Override
@@ -73,4 +72,10 @@ public class MobAnimationScheduler extends BaseScheduler {
animationLock.unlock();
}
}
@Override
public void dispose() {
animationLock.dispose();
super.dispose();
}
}

View File

@@ -25,7 +25,6 @@ import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReentrantLock;
@@ -35,7 +34,7 @@ import net.server.audit.locks.MonitoredReentrantLock;
*/
public class MobStatusScheduler extends BaseScheduler {
private Map<MonsterStatusEffect, MobStatusOvertimeEntry> registeredMobStatusOvertime = new HashMap<>();
private Lock overtimeStatusLock = new MonitoredReentrantLock(MonitoredLockType.CHANNEL_OVTSTATUS, true);
private MonitoredReentrantLock overtimeStatusLock = new MonitoredReentrantLock(MonitoredLockType.CHANNEL_OVTSTATUS, true);
private class MobStatusOvertimeEntry {
private int procCount;
@@ -48,11 +47,11 @@ public class MobStatusScheduler extends BaseScheduler {
r = run;
}
protected void update() {
protected void update(List<Runnable> toRun) {
procCount++;
if(procCount >= procLimit) {
procCount = 0;
r.run();
toRun.add(r);
}
}
}
@@ -63,6 +62,8 @@ public class MobStatusScheduler extends BaseScheduler {
super.addListener(new SchedulerListener() {
@Override
public void removedScheduledEntries(List<Object> toRemove, boolean update) {
List<Runnable> toRun = new ArrayList<>();
overtimeStatusLock.lock();
try {
for(Object mseo : toRemove) {
@@ -74,12 +75,16 @@ public class MobStatusScheduler extends BaseScheduler {
// it's probably ok to use one thread for both management & overtime actions
List<MobStatusOvertimeEntry> mdoeList = new ArrayList<>(registeredMobStatusOvertime.values());
for(MobStatusOvertimeEntry mdoe : mdoeList) {
mdoe.update();
mdoe.update(toRun);
}
}
} finally {
overtimeStatusLock.unlock();
}
for(Runnable r : toRun) {
r.run();
}
}
});
}
@@ -102,4 +107,10 @@ public class MobStatusScheduler extends BaseScheduler {
public void interruptMobStatus(MonsterStatusEffect mse) {
interruptEntry(mse);
}
@Override
public void dispose() {
overtimeStatusLock.dispose();
super.dispose();
}
}