package net.johnewart.gearman.server.net;
import com.codahale.metrics.annotation.Metered;
import com.codahale.metrics.annotation.Timed;
import com.google.common.collect.ImmutableList;
import io.netty.channel.Channel;
import net.johnewart.gearman.common.Job;
import net.johnewart.gearman.common.JobStatus;
import net.johnewart.gearman.common.interfaces.EngineClient;
import net.johnewart.gearman.common.packets.Packet;
import net.johnewart.gearman.common.packets.request.EchoRequest;
import net.johnewart.gearman.common.packets.request.GetStatus;
import net.johnewart.gearman.common.packets.request.OptionRequest;
import net.johnewart.gearman.common.packets.request.SubmitJob;
import net.johnewart.gearman.common.packets.response.EchoResponse;
import net.johnewart.gearman.common.packets.response.JobAssign;
import net.johnewart.gearman.common.packets.response.JobAssignUniq;
import net.johnewart.gearman.common.packets.response.JobCreated;
import net.johnewart.gearman.common.packets.response.NoJob;
import net.johnewart.gearman.common.packets.response.OptionResponse;
import net.johnewart.gearman.common.packets.response.StatusRes;
import net.johnewart.gearman.common.packets.response.WorkCompleteResponse;
import net.johnewart.gearman.common.packets.response.WorkDataResponse;
import net.johnewart.gearman.common.packets.response.WorkExceptionResponse;
import net.johnewart.gearman.common.packets.response.WorkResponse;
import net.johnewart.gearman.common.packets.response.WorkStatus;
import net.johnewart.gearman.common.packets.response.WorkWarningResponse;
import net.johnewart.gearman.constants.JobPriority;
import net.johnewart.gearman.constants.PacketType;
import net.johnewart.gearman.engine.core.JobManager;
import net.johnewart.gearman.engine.exceptions.EnqueueException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ConcurrentHashMap;
/**
* Serves as an interface between the packet handler and the job manager
* General flow:
* Netty - CODEC - PacketHandler - NetworkManager - JobManager
*
*/
public class NetworkManager
{
private final JobManager jobManager;
private final ConcurrentHashMap<Channel, NetworkEngineWorker> workers;
private final ConcurrentHashMap<Channel, NetworkEngineClient> clients;
private static Logger LOG = LoggerFactory.getLogger(NetworkManager.class);
public NetworkManager(JobManager jobManager)
{
this.jobManager = jobManager;
this.workers = new ConcurrentHashMap<>();
this.clients = new ConcurrentHashMap<>();
}
public synchronized void channelDisconnected(Channel channel)
{
if (workers.containsKey(channel))
{
NetworkEngineWorker worker = workers.get(channel);
jobManager.unregisterWorker(worker);
workers.remove(channel);
}
else if (clients.containsKey(channel))
{
EngineClient client = clients.get(channel);
jobManager.unregisterClient(client);
clients.remove(channel);
}
}
public void sleepingWorker(Channel channel)
{
// Remove from any worker lists
if (workers.containsKey(channel))
{
NetworkEngineWorker worker = workers.get(channel);
jobManager.markWorkerAsAsleep(worker);
}
}
public void registerAbility(String functionName, Channel channel)
{
NetworkEngineWorker worker = findOrCreateWorker(channel);
worker.addAbility(functionName);
jobManager.registerWorkerAbility(functionName, worker);
}
public void unregisterAbility(String functionName, Channel channel)
{
if (workers.containsKey(channel))
{
NetworkEngineWorker worker = workers.get(channel);
worker.removeAbility(functionName);
jobManager.unregisterWorkerAbility(functionName, worker);
}
}
public void nextJobForWorker(Channel channel, boolean uniqueID)
{
if (workers.containsKey(channel))
{
NetworkEngineWorker worker = workers.get(channel);
Job nextJob = jobManager.nextJobForWorker(worker);
if (nextJob != null)
{
Packet packet;
if (uniqueID)
{
packet = createJobAssignUniqPacket(nextJob);
}
else
{
packet = createJobAssignPacket(nextJob);
}
try
{
worker.send(packet);
}
catch (Exception e)
{
LOG.error("Unable to write to worker. Re-enqueing job.");
try
{
jobManager.reEnqueueJob(nextJob);
}
catch (EnqueueException ee)
{
LOG.error("Error re-enqueing after failed transmission: ", ee);
}
}
}
else
{
worker.send(new NoJob());
}
}
}
@Timed
@Metered
public void createJob(SubmitJob packet, Channel channel)
{
EngineClient client = findOrCreateClient(channel);
String funcName = packet.getFunctionName();
String uniqueID = packet.getUniqueId();
byte[] data = packet.getData();
JobPriority priority = packet.getPriority();
boolean isBackground = packet.isBackground();
long timeToRun = -1;
if (packet.getType() == PacketType.SUBMIT_JOB_EPOCH)
{
timeToRun = packet.getEpoch();
}
// This could return an existing job, or the newly generated one
final Job inputJob = new Job(funcName, uniqueID, data, priority, isBackground, timeToRun);
try
{
Job storedJob = jobManager.storeJobForClient(inputJob, client);
if (storedJob != null)
{
client.setCurrentJob(storedJob);
client.send(createJobCreatedPacket(storedJob));
}
else
{
// TODO: send a failure/error packet?
}
} catch (EnqueueException e) {
// TODO: Send a failure / error packet?
}
}
public void checkJobStatus(GetStatus getStatus, Channel channel)
{
EngineClient client = findOrCreateClient(channel);
JobStatus jobStatus = jobManager.checkJobStatus(getStatus.jobHandle.get());
StatusRes result = new StatusRes(jobStatus);
client.send(result);
}
public void updateJobStatus(WorkStatus workStatus)
{
String jobHandle = workStatus.getJobHandle();
int completeNumerator = workStatus.getCompleteNumerator();
int completeDenominator = workStatus.getCompleteDenominator();
jobManager.updateJobStatus(jobHandle, completeNumerator, completeDenominator);
}
private EngineClient findOrCreateClient(Channel channel)
{
NetworkEngineClient client;
if (clients.containsKey(channel))
{
client = clients.get(channel);
}
else
{
client = new NetworkEngineClient(channel);
clients.put(channel, client);
}
return client;
}
private NetworkEngineWorker findOrCreateWorker(Channel channel)
{
NetworkEngineWorker worker;
if (workers.containsKey(channel))
{
worker = workers.get(channel);
}
else
{
worker = new NetworkEngineWorker(channel);
workers.put(channel, worker);
}
return worker;
}
public void workResponse(WorkResponse response, Channel channel)
{
if (workers.containsKey(channel))
{
NetworkEngineWorker worker = workers.get(channel);
Job currentJob = jobManager.getJobByJobHandle(response.getJobHandle());
if (currentJob != null)
{
switch (response.getType())
{
case WORK_COMPLETE:
jobManager.handleWorkCompletion(currentJob, ((WorkCompleteResponse) response).getData());
break;
case WORK_DATA:
jobManager.handleWorkData(currentJob, ((WorkDataResponse) response).getData());
break;
case WORK_EXCEPTION:
jobManager.handleWorkException(currentJob, ((WorkExceptionResponse) response).getException());
break;
case WORK_WARNING:
jobManager.handleWorkWarning(currentJob, ((WorkWarningResponse) response).getData());
break;
case WORK_FAIL:
jobManager.handleWorkFailure(currentJob);
break;
default:
break;
}
}
}
}
public JobManager getJobManager()
{
return jobManager;
}
public final Packet createJobAssignPacket(Job job)
{
return new JobAssign(job.getJobHandle(), job.getFunctionName(), job.getData());
}
public final Packet createJobAssignUniqPacket(Job job)
{
return new JobAssignUniq(job.getJobHandle(), job.getFunctionName(), job.getUniqueID(), job.getData());
}
public final Packet createJobCreatedPacket(Job job)
{
return new JobCreated(job.getJobHandle());
}
public final Packet createWorkStatusPacket(Job job)
{
return new WorkStatus(job.getJobHandle(), job.getNumerator(), job.getDenominator());
}
public final Packet createStatusResponsePacket(Job job)
{
boolean isRunning = job.isRunning();
boolean knownState = true;
int numerator = job.getNumerator();
int denominator = job.getDenominator();
String jobHandle = job.getJobHandle();
if (numerator == 0 && denominator == 0)
{
knownState = false;
}
return new StatusRes(jobHandle, isRunning, knownState, numerator, denominator);
}
public ImmutableList<NetworkEngineClient> getClientList()
{
return ImmutableList.copyOf(clients.values());
}
public ImmutableList<NetworkEngineWorker> getWorkerList()
{
return ImmutableList.copyOf(workers.values());
}
public void handleEchoRequest(EchoRequest request, Channel channel)
{
EchoResponse response = new EchoResponse(request);
channel.write(response);
}
public void resetWorkerAbilities(Channel channel)
{
NetworkEngineWorker worker = findOrCreateWorker(channel);
jobManager.resetWorkerAbilities(worker);
}
public void handleOptionRequest(OptionRequest packet, Channel channel)
{
//TODO: mark that the client wants exceptions (why would it not want them?)
OptionResponse response = new OptionResponse(packet.getOption());
channel.write(response);
}
}