/* * Jobs.java * * Copyright (C) 2015 Pixelgaffer * * This work is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation; either version 2 of the License, or any later * version. * * This work 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 version 2 and version 3 of the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.pixelgaffer.turnierserver.backend; import static org.pixelgaffer.turnierserver.networking.bwprotocol.WorkerCommandAnswer.SUCCESS; import static org.pixelgaffer.turnierserver.networking.messages.WorkerCommand.COMPILE; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.NonNull; import org.pixelgaffer.turnierserver.Airbrake; import org.pixelgaffer.turnierserver.Parsers; import org.pixelgaffer.turnierserver.backend.server.BackendFrontendConnectionHandler; import org.pixelgaffer.turnierserver.backend.server.message.BackendFrontendCommand; import org.pixelgaffer.turnierserver.backend.server.message.BackendFrontendCommandProcessed; import org.pixelgaffer.turnierserver.backend.server.message.BackendFrontendResult; import org.pixelgaffer.turnierserver.networking.bwprotocol.WorkerCommandAnswer; import org.pixelgaffer.turnierserver.networking.messages.WorkerCommand; /** * Diese Klasse speichert Informationen zu den aktuell ausgeführten * Kompilierungsaufträgen. */ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class Jobs { /** Die Liste mit Befehlen die noch nicht processed wurden. */ private static final List<BackendFrontendCommand> pending = new ArrayList<>(); /** Die Liste aller Jobs. */ private static final List<Job> jobs = new ArrayList<>(); /** Die Map mit den UUIDs und den zugehörigen Jobs. */ private static final Map<UUID, Job> jobUuids = new HashMap<>(); /** Die Map mit den Request IDs und den zugehörigen Jobs. */ private static final Map<Integer, Job> jobRequestIds = new HashMap<>(); /** * Schreibt alle aktuell bekannten Jobs in die angegebene Datei. */ public static void storeJobs (File file) throws IOException { PrintStream out = new PrintStream(file); synchronized (jobs) { for (Job job : jobs) { out.println(Parsers.getParser(false).parse(job.getFrontendCommand(), false)); } } synchronized (pending) { for (BackendFrontendCommand cmd : pending) { out.println(Parsers.getParser(false).parse(cmd, false)); } } out.close(); } /** * Liest alle gespeicherten Jobs ein und processet diese. */ public static void restoreJobs (File file) throws IOException { BufferedReader in = new BufferedReader(new FileReader(file)); String line; while ((line = in.readLine()) != null) { BackendFrontendCommand cmd = Parsers.getParser(false).parse(line.getBytes(), BackendFrontendCommand.class); BackendMain.getLogger().info("Job wiederhergestellt: " + cmd); processCommand(cmd); } in.close(); } /** * Gibt die RequestId des Jobs mit der angegebenen UUID zurück. * * @throws NullPointerException Wenn kein solcher Job gefunden wurde. */ public static final int findRequestId (UUID uuid) { return jobUuids.get(uuid).getRequestId(); } /** * Gibt die UUID des Jobs mit der angegebenen RequestId zurück. * * @throws NullPointerException Wenn kein solcher Job gefunden wurde. */ public static final UUID findUuid (int requestId) { return jobRequestIds.get(requestId).getUuid(); } /** * Fügt den Job zur Liste der Jobs hinzu. */ private static void addJob (@NonNull Job job) { synchronized (jobs) { jobs.add(job); jobUuids.put(job.getUuid(), job); jobRequestIds.put(job.getRequestId(), job); } } /** * Verarbeitet den angegebenen Command und startet dafür die nötigen Jobs. * Diese Methode startet dafür einen neuen Thread und arbeitet somit * asynchron. */ public static void processCommand (@NonNull BackendFrontendCommand cmd) { new Thread( () -> { synchronized (pending) { pending.add(cmd); } if (cmd.getAction().equals("compile")) { try { WorkerConnection worker = Workers.getCompilableWorker(); WorkerCommand wcmd = worker.compile(cmd.getId(), cmd.getLanguage(), cmd.getGametype()); Job job = new Job(wcmd, cmd, worker); addJob(job); BackendFrontendConnectionHandler.getFrontend().sendMessage( Parsers.getFrontend() .parse(new BackendFrontendCommandProcessed(cmd.getRequestid()), false)); } catch (Exception e) { Airbrake.log(e).printStackTrace(); BackendFrontendResult result = new BackendFrontendResult(cmd.getRequestid(), false, null, e); try { BackendFrontendConnectionHandler.getFrontend().sendMessage( Parsers.getFrontend().parse(result, false)); } catch (IOException e1) { Airbrake.log(e1).printStackTrace(); } } } else if (cmd.getAction().equals("start")) { try { Games.startGame(cmd.getGametype(), cmd.getRequestid(), false, cmd.getLanguages(), cmd.getAis()); BackendFrontendConnectionHandler.getFrontend().sendMessage( Parsers.getFrontend().parse( new BackendFrontendCommandProcessed(cmd.getRequestid()), false)); } catch (Exception e) { Airbrake.log(e).printStackTrace(); BackendFrontendResult result = new BackendFrontendResult(cmd.getRequestid(), false, null, e); try { BackendFrontendConnectionHandler.getFrontend().sendMessage( Parsers.getFrontend().parse(result, false)); } catch (IOException e1) { Airbrake.log(e1).printStackTrace(); } } } else if (cmd.getAction().equals("qualify")) { try { Games.startQualifyGame(cmd.getGametype(), cmd.getRequestid(), cmd.getLanguage(), cmd.getId(), cmd.getQualilang()); BackendFrontendConnectionHandler.getFrontend().sendMessage( Parsers.getFrontend().parse( new BackendFrontendCommandProcessed(cmd.getRequestid()), false)); } catch (Exception e) { Airbrake.log(e).printStackTrace(); BackendFrontendResult result = new BackendFrontendResult(cmd.getRequestid(), false, null, e); try { BackendFrontendConnectionHandler.getFrontend().sendMessage( Parsers.getFrontend().parse(result, false)); } catch (IOException e1) { Airbrake.log(e1).printStackTrace(); } } } else if (cmd.getAction().equals("tournament")) { BackendMain.getLogger().todo("Check that no current tournament is running"); BackendMain.getLogger().info("Starte Turnier " + cmd.getTournament()); new Tournament(cmd.getTournament(), cmd.getGametype(), cmd.getRequestid()); } else { BackendMain.getLogger().critical("Unknown action from Frontend: " + cmd.getAction()); Airbrake.log("Unknown action from Frontend: " + cmd.getAction()); } synchronized (pending) { pending.remove(cmd); } } , "JobProcessor").start(); } /** * Muss aufgerufen werden, wenn ein Job fertig ist. Entfernt den Job aus * der * Liste und benachrichtigt das Frontend. */ public static void jobFinished (@NonNull WorkerCommandAnswer answer) throws IOException { UUID uuid = answer.getUuid(); Job job = jobUuids.get(uuid); if (job == null) { BackendMain.getLogger().critical("Couldn't find job with UUID " + uuid); return; } if (job.getWorkerCommand().getAction() == COMPILE) job.getWorker().setCompiling(false); int requestId = job.getRequestId(); BackendFrontendResult result = new BackendFrontendResult(requestId, answer.getWhat() == SUCCESS, uuid); BackendFrontendConnectionHandler.getFrontend().sendMessage(Parsers.getFrontend().parse(result, false)); synchronized (jobs) { jobs.remove(job); jobUuids.remove(uuid); jobRequestIds.remove(requestId); } } /** * Muss aufgerufen werden, wenn sich ein Worker disconnected. Alle * ausstehenden Jobs des Workers werden anschließend neu gestartet. */ public static void workerDisconnected (@NonNull WorkerConnection worker) { synchronized (jobs) { for (int i = 0; i < jobs.size();) { if (jobs.get(i).getWorker().equals(worker)) { processCommand(jobs.get(i).getFrontendCommand()); jobs.remove(i); } else i++; } } } }