/*
* This file is part of aion-emu <aion-emu.com>.
*
* aion-emu 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.
*
* aion-emu 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 aion-emu. If not, see <http://www.gnu.org/licenses/>.
*/
package com.aionemu.commons.network;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import com.aionemu.commons.network.packet.BaseClientPacket;
/**
* Packet Processor responsible for executing packets in correct order with respecting rules: - 1 packet / client at one
* time. - execute packets in received order.
*
* @author -Nemesiss-
* @param <T>
* AConnection - owner of client packets.
*
*/
public class PacketProcessor<T extends AConnection>
{
/**
* Logger for PacketProcessor
*/
private static final Logger log = Logger.getLogger(PacketProcessor.class.getName());
/**
* When one working thread should be killed.
*/
private final static int reduceThreshold = 3;
/**
* When one working thread should be created.
*/
private final static int increaseThreshold = 50;
/**
* Lock for synchronization.
*/
private final Lock lock = new ReentrantLock();
/**
* Not Empty condition.
*/
private final Condition notEmpty = lock.newCondition();
/**
* Queue of packet that will be executed in correct order.
*/
private final List<BaseClientPacket<T>> packets = new LinkedList<BaseClientPacket<T>>();
/**
* Working threads.
*/
private final List<Thread> threads = new ArrayList<Thread>();
/**
* minimum number of working Threads
*/
private final int minThreads;
/**
* maximum number of working Threads
*/
private final int maxThreads;
/**
* Create and start PacketProcessor responsible for executing packets.
*
* @param minThreads
* - minimum number of working Threads.
* @param maxThreads
* - maximum number of working Threads.
*/
public PacketProcessor(int minThreads, int maxThreads)
{
if(minThreads <= 0)
minThreads = 1;
if(maxThreads < minThreads)
maxThreads = minThreads;
this.minThreads = minThreads;
this.maxThreads = maxThreads;
if(minThreads != maxThreads)
startCheckerThread();
for(int i = 0; i < minThreads; i++)
newThread();
}
/**
* Start Checker Thread. Checker is responsible for increasing / reducing PacketProcessor Thread count based on
* Runtime needs.
*/
private void startCheckerThread()
{
new Thread(new CheckerTask(), "PacketProcessor:Checker").start();
}
/**
* Create and start new PacketProcessor Thread, but only if there wont be more working Threads than "maxThreads"
*
* @return true if new Thread was created.
*/
private boolean newThread()
{
if(threads.size() >= maxThreads)
return false;
String name = "PacketProcessor:" + threads.size();
log.debug("Creating new PacketProcessor Thread: " + name);
Thread t = new Thread(new PacketProcessorTask(), name);
threads.add(t);
t.start();
return true;
}
/**
* Kill one PacketProcessor Thread, but only if there are more working Threads than "minThreads"
*/
private void killThread()
{
if(threads.size() < minThreads)
{
Thread t = threads.remove((threads.size() - 1));
log.debug("Killing PacketProcessor Thread: " + t.getName());
t.interrupt();
}
}
/**
* Add packet to execution queue and execute it as soon as possible on another Thread.
*
* @param packet
* that will be executed.
*/
public final void executePacket(BaseClientPacket<T> packet)
{
lock.lock();
try
{
packets.add(packet);
notEmpty.signal();
}
finally
{
lock.unlock();
}
}
/**
* Return first packet available for execution with respecting rules: - 1 packet / client at one time. - execute
* packets in received order.
*
* @return first available BaseClientPacket
*/
private BaseClientPacket<T> getFirstAviable()
{
for(;;)
{
while(packets.isEmpty())
notEmpty.awaitUninterruptibly();
ListIterator<BaseClientPacket<T>> it = packets.listIterator();
while(it.hasNext())
{
BaseClientPacket<T> packet = it.next();
if(packet.getConnection().tryLockConnection())
{
it.remove();
return packet;
}
}
notEmpty.awaitUninterruptibly();
}
}
/**
* Packet Processor Task that will execute packet with respecting rules: - 1 packet / client at one time. - execute
* packets in received order.
*
* @author -Nemesiss-
*
*/
private final class PacketProcessorTask implements Runnable
{
/**
* {@inheritDoc}
*/
@Override
public void run()
{
BaseClientPacket<T> packet = null;
for(;;)
{
lock.lock();
try
{
if(packet != null)
packet.getConnection().unlockConnection();
/* thread killed */
if(Thread.interrupted())
return;
packet = getFirstAviable();
}
finally
{
lock.unlock();
}
packet.run();
}
}
}
/**
* Checking if PacketProcessor is busy or idle and increasing / reducing numbers of threads.
*
* @author -Nemesiss-
*
*/
private final class CheckerTask implements Runnable
{
/**
* How often CheckerTask should do check.
*/
private final int sleepTime = 60 * 1000;
/**
* Number of packets waiting for execution on last check.
*/
private int lastSize = 0;
/**
* {@inheritDoc}
*/
@Override
public void run()
{
/* Sleep for some time */
try
{
Thread.sleep(sleepTime);
}
catch(InterruptedException e)
{
// we dont care
}
/* Number of packets waiting for execution */
int sizeNow = packets.size();
if(sizeNow < lastSize)
{
if(sizeNow < reduceThreshold)
{
// too much threads
killThread();
}
}
else if(sizeNow > lastSize && sizeNow > increaseThreshold)
{
// too low threads
if(!newThread() && sizeNow >= increaseThreshold * 3)
log
.info("Lagg detected! ["
+ sizeNow
+ " client packets are waiting for execution]. You should consider increasing PacketProcessor maxThreads or hardware upgrade.");
}
lastSize = sizeNow;
}
}
}