/* -*- tab-width: 4 -*- * * Electric(tm) VLSI Design System * * File: ServerJobManager.java * * Copyright (c) 2003 Sun Microsystems and Static Free Software * * Electric(tm) 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. * * Electric(tm) 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 Electric(tm); see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, Mass 02111-1307, USA. */ package com.sun.electric.tool; import com.sun.electric.database.hierarchy.Cell; import com.sun.electric.database.hierarchy.EDatabase; import com.sun.electric.database.hierarchy.Library; import com.sun.electric.Main; import com.sun.electric.database.id.CellId; import com.sun.electric.database.id.LibId; import com.sun.electric.database.id.TechId; import com.sun.electric.database.topology.Geometric; import com.sun.electric.database.variable.EditWindow_; import com.sun.electric.database.variable.UserInterface; import com.sun.electric.technology.Technology; import com.sun.electric.tool.user.ActivityLogger; import com.sun.electric.tool.user.ErrorLogger; import com.sun.electric.tool.user.MessagesStream; import com.sun.electric.tool.user.User; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; /** * */ public class ServerJobManager { private static final String CLASS_NAME = Job.class.getName(); private static final int defaultNumThreads = Math.max(2, Runtime.getRuntime().availableProcessors()); private final ReentrantLock lock = new ReentrantLock(); void lock() { lock.lock(); } void unlock() { lock.unlock(); } /** mutex for database synchronization. */ private final Condition databaseChangesMutex = lock.newCondition(); /** started jobs */ private final ArrayList<EJob> startedJobs = new ArrayList<EJob>(); /** waiting jobs */ private final ArrayList<EJob> waitingJobs = new ArrayList<EJob>(); private final ServerSocket serverSocket; // private final ArrayList<EJob> finishedJobs = new ArrayList<EJob>(); final ArrayList<Client> serverConnections = new ArrayList<Client>(); private int passiveConnections; // private final UserInterface redirectInterface = new UserInterfaceRedirect(); private int numThreads; private final int maxNumThreads; private boolean runningChangeJob; // private boolean guiChanged; private boolean signalledEThread; private static int maxNumberOfThreads; /** Creates a new instance of JobPool */ ServerJobManager(int recommendedNumThreads, String loggingFilePath, boolean pipe, int socketPort) { maxNumThreads = initThreads(recommendedNumThreads); maxNumberOfThreads = maxNumThreads; if (Job.currentUI != null) initCurrentUI(Job.currentUI); if (loggingFilePath != null) initSnapshotLogging(new File(loggingFilePath)); if (pipe) initPipe(); ServerSocket serverSocket = null; if (socketPort > 0) { try { serverSocket = new ServerSocket(socketPort); System.out.println("ServerSocket waits for port " + socketPort); } catch (IOException e) { System.out.println("ServerSocket mode failure: " + e.getMessage()); } } this.serverSocket = serverSocket; } ServerJobManager() { this(0, null, false, 0); serverConnections.add(new Client(0) {}); } private int initThreads(int recommendedNumThreads) { int maxNumThreads = defaultNumThreads; if (recommendedNumThreads > 0) maxNumThreads = recommendedNumThreads; Job.logger.logp(Level.FINE, CLASS_NAME, "initThreads", "maxNumThreads=" + maxNumThreads); return maxNumThreads; } void initCurrentUI(AbstractUserInterface currentUI) { lock(); try { assert currentUI.connectionId == 0; assert serverConnections.isEmpty(); serverConnections.add(currentUI); } finally { unlock(); } currentUI.startDispatcher(); } void initSnapshotLogging(File loggingFile) { StreamClient conn; lock(); try { int connectionId = serverConnections.size(); FileOutputStream out = new FileOutputStream(loggingFile); System.err.println("Writing snapshot log to " + loggingFile); ActivityLogger.logMessage("Writing snapshot log to " + loggingFile); conn = new StreamClient(connectionId, null, new BufferedOutputStream(out)); serverConnections.add(conn); passiveConnections++; } catch (IOException e) { System.err.println("Failed to create snapshot log file:" + e.getMessage()); return; } finally { unlock(); } conn.start(); } void initPipe() { StreamClient conn; OutputStream stdout = System.out; MessagesStream.getMessagesStream(); lock(); try { int connectionId = serverConnections.size(); conn = new StreamClient(connectionId, System.in, stdout); serverConnections.add(conn); } finally { unlock(); } conn.start(); } void connectionClosed() { lock(); try { passiveConnections++; if (passiveConnections == serverConnections.size()) { try { ActivityLogger.finished(); } catch (Exception e) { } System.exit(0); } } finally { unlock(); } } /** Add job to list of jobs */ void addJob(EJob ejob, boolean onMySnapshot) { lock(); try { if (onMySnapshot) waitingJobs.add(0, ejob); else waitingJobs.add(ejob); setEJobState(ejob, EJob.State.WAITING, onMySnapshot ? EJob.WAITING_NOW : "waiting"); invokeEThread(); } finally { unlock(); } } /** Remove job from list of jobs */ void removeJob(Job j) { EJob ejob = j.ejob; lock(); try { switch (j.ejob.state) { case WAITING: setEJobState(ejob, EJob.State.SERVER_DONE, null); case SERVER_DONE: setEJobState(ejob, EJob.State.CLIENT_DONE, null); case CLIENT_DONE: // finishedJobs.remove(j.ejob); // if (!Job.BATCHMODE && !guiChanged) // SwingUtilities.invokeLater(this); // guiChanged = true; break; } } finally { unlock(); } } void setProgress(EJob ejob, String progress) { lock(); try { if (ejob.state == EJob.State.RUNNING) setEJobState(ejob, EJob.State.RUNNING, progress); } finally { unlock(); } } /** get all jobs iterator */ Iterator<Job> getAllJobs() { lock(); try { ArrayList<Job> jobsList = new ArrayList<Job>(); for (EJob ejob: startedJobs) { Job job = ejob.getJob(); if (job != null) jobsList.add(job); } for (EJob ejob: waitingJobs) { Job job = ejob.getJob(); if (job != null) jobsList.add(job); } return jobsList.iterator(); } finally { unlock(); } } List<Job.Inform> getAllJobInforms() { lock(); try { ArrayList<Job.Inform> jobsList = new ArrayList<Job.Inform>(); for (EJob ejob: startedJobs) { Job job = ejob.getJob(); if (job != null) jobsList.add(job.getInform()); else jobsList.add(ejob.getInform()); } for (EJob ejob: waitingJobs) { Job job = ejob.getJob(); if (job != null) jobsList.add(job.getInform()); else jobsList.add(ejob.getInform()); } return jobsList; } finally { unlock(); } } //--------------------------PRIVATE JOB METHODS-------------------------- private void invokeEThread() { if (signalledEThread || startedJobs.size() >= maxNumThreads) return; if (!canDoIt()) return; if (startedJobs.size() < numThreads) databaseChangesMutex.signal(); else{ new EThread(numThreads++); } signalledEThread = true; } // private EJob selectTerminateIt() { // lock(); // try { // for (int i = 0; i < finishedJobs.size(); i++) { // EJob ejob = finishedJobs.get(i); // if (ejob.state == EJob.State.CLIENT_DONE) continue; //// finishedJobs.remove(i); // return ejob; // } // } finally { // unlock(); // } // return null; // } // // void wantUpdateGui() { // lock(); // try { // this.guiChanged = true; // } finally { // unlock(); // } // } // // private boolean guiChanged() { // lock(); // try { // boolean b = this.guiChanged; // this.guiChanged = false; // return b; // } finally { // unlock(); // } // } private boolean canDoIt() { if (waitingJobs.isEmpty()) return false; EJob ejob = waitingJobs.get(0); return startedJobs.isEmpty() || !runningChangeJob && ejob.isExamine(); } private void setEJobState(EJob ejob, EJob.State newState, String info) { Job.logger.logp(Level.FINE, CLASS_NAME, "setEjobState", newState + " "+ ejob.jobName); EJob.State oldState = ejob.state; switch (newState) { case WAITING: break; case RUNNING: if (oldState == EJob.State.RUNNING) { assert oldState == EJob.State.RUNNING; ejob.progress = info; if (info.equals(EJob.ABORTING)) ejob.serverJob.scheduledToAbort = true; } break; case SERVER_DONE: boolean removed; if (oldState == EJob.State.WAITING) { removed = waitingJobs.remove(ejob); } else { assert oldState == EJob.State.RUNNING; removed = startedJobs.remove(ejob); if (startedJobs.isEmpty()) runningChangeJob = false; } assert removed; // if (Job.threadMode != Job.Mode.BATCH && ejob.client == null) // finishedJobs.add(ejob); ejob.state = newState; Client.fireServerEvent(new Client.EJobEvent(ejob.jobKey, ejob.jobName, ejob.getJob().getTool(), ejob.jobType, ejob.serializedJob, ejob.doItOk, ejob.serializedResult, ejob.newSnapshot, ejob.state)); break; case CLIENT_DONE: assert oldState == EJob.State.SERVER_DONE; // if (ejob.clientJob.deleteWhenDone) // finishedJobs.remove(ejob); } ejob.state = newState; List<Job.Inform> jobs = getAllJobInforms(); Client.fireServerEvent(new Client.JobQueueEvent(jobs.toArray(new Job.Inform[jobs.size()]))); // if (!Job.BATCHMODE && !guiChanged) // SwingUtilities.invokeLater(this); // guiChanged = true; Job.logger.exiting(CLASS_NAME, "setJobState"); } // private boolean isChangeJobQueuedOrRunning() { // lock(); // try { // for (EJob ejob: startedJobs) { // Job job = ejob.getJob(); // if (job != null && job.finished) continue; // if (ejob.jobType == Job.Type.CHANGE) return true; // } // for (EJob ejob: waitingJobs) { // if (ejob.jobType == Job.Type.CHANGE) return true; // } // return false; // } finally { // unlock(); // } // } public void runLoop(Job initialJob) { initialJob.startJob(); if (serverSocket == null) return; try { // Wait for connections for (;;) { Socket socket = serverSocket.accept(); int connectionId = serverConnections.size(); StreamClient conn; lock(); try { conn = new StreamClient(connectionId, socket.getInputStream(), socket.getOutputStream()); serverConnections.add(conn); } finally { unlock(); } System.out.println("Accepted connection " + connectionId); conn.start(); } } catch (IOException e) { e.printStackTrace(System.out); } } // /** // * This method is executed in Swing thread. // */ // public void run() { // assert !Job.BATCHMODE; // Job.logger.logp(Level.FINE, CLASS_NAME, "run", "ENTER"); // while (guiChanged()) { // ArrayList<Job.Inform> jobs = new ArrayList<Job.Inform>(); // for (Iterator<Job> it = Job.getAllJobs(); it.hasNext();) { // Job j = it.next(); // if (j.getDisplay()) { // jobs.add(j.getInform()); // } // } // JobTree.update(jobs); // TopLevel.setBusyCursor(isChangeJobQueuedOrRunning()); //// for (;;) { //// EJob ejob = selectTerminateIt(); //// if (ejob == null) break; //// //// Job.logger.logp(Level.FINE, CLASS_NAME, "run", "terminate {0}", ejob.jobName); //// Job.runTerminate(ejob); //// setEJobState(ejob, EJob.State.CLIENT_DONE, null); //// Job.logger.logp(Level.FINE, CLASS_NAME, "run", "terminated {0}", ejob.jobName); //// } // Job.logger.logp(Level.FINE, CLASS_NAME, "run", "wantToRedoJobTree"); // } // Job.logger.logp(Level.FINE, CLASS_NAME, "run", "EXIT"); // } EJob selectEJob(EJob finishedEJob) { EJob selectedEJob = null; lock(); try { if (finishedEJob != null) { // EJob.State state = finishedEJob.state; setEJobState(finishedEJob, EJob.State.SERVER_DONE, "done"); } for (;;) { signalledEThread = false; // Search for examine if (canDoIt()) { EJob ejob = waitingJobs.remove(0); startedJobs.add(ejob); if (ejob.isExamine()) { assert !runningChangeJob; invokeEThread(); } else { assert startedJobs.size() == 1; assert !runningChangeJob; runningChangeJob = true; } setEJobState(ejob, EJob.State.RUNNING, "running"); selectedEJob = ejob; break; } Job.logger.logp(Level.FINE, CLASS_NAME, "selectConnection", "pause"); databaseChangesMutex.awaitUninterruptibly(); Job.logger.logp(Level.FINE, CLASS_NAME, "selectConnection", "resume"); } } finally { unlock(); } return selectedEJob; } /*private*/ static class UserInterfaceRedirect implements UserInterface { private final Job.Key jobKey; private final Client client; private final EDatabase database; TechId curTechId; LibId curLibId; CellId curCellId; private String progressNote; private int progressValue = -1; UserInterfaceRedirect(Job.Key jobKey) { this.jobKey = jobKey; client = Job.serverJobManager.serverConnections.get(jobKey.clientId); database = jobKey.doItOnServer ? EDatabase.serverDatabase() : EDatabase.clientDatabase(); } UserInterfaceRedirect(Job.Key jobKey, AbstractUserInterface client) { this.jobKey = jobKey; this.client = client; assert !jobKey.doItOnServer; database = EDatabase.clientDatabase(); } UserInterfaceRedirect(EDatabase database) { jobKey = null; client = null; this.database = database; } void setCurrents(Job job) { assert jobKey == job.getKey(); curTechId = job.curTechId; curLibId = job.curLibId; curCellId = job.curCellId; } private static void printStackTrace(String methodName) { if (!Job.getDebug()) return; System.out.println("UserInterface." + methodName + " was called from DatabaseChangesThread"); ActivityLogger.logException(new IllegalStateException()); } /** * Method to start the display of a progress dialog. * @param msg the message to show in the progress dialog. * @param the file being read (null if not reading a file). */ public void startProgressDialog(String msg, String filePath) { progressValue = -1; Client.fireServerEvent(new Client.StartProgressDialogEvent(msg, filePath)); } /** * Method to stop the progress bar */ public void stopProgressDialog() { progressValue = -1; Client.fireServerEvent(new Client.StopProgressDialogEvent()); } /** * Method to update the progress bar * @param pct the percentage done (from 0 to 100). */ public void setProgressValue(int pct) { if (pct == progressValue) return; progressValue = pct; Client.fireServerEvent(new Client.ProgressValueEvent(pct)); } /** * Method to set a text message in the progress dialog. * @param message the new progress message. */ public void setProgressNote(String message) { progressNote = message; progressValue = -1; Client.fireServerEvent(new Client.ProgressNoteEvent(message)); } /** * Method to get text message in the progress dialog. * @return text message in the progress dialog. */ public String getProgressNote() { return progressNote; } public Job.Key getJobKey() { return jobKey; } public EDatabase getDatabase() { return database; } public Technology getCurrentTechnology() { Technology tech = null; if (curTechId != null) tech = database.getTech(curTechId); if (tech == null) tech = database.getTechPool().findTechnology(User.getDefaultTechnology()); if (tech == null) tech = database.getTechPool().findTechnology("mocmos"); return tech; } public Library getCurrentLibrary() { return curLibId != null ? database.getLib(curLibId) : null; } public EditWindow_ getCurrentEditWindow_() { printStackTrace("getCurrentEditWindow"); return null; } public EditWindow_ needCurrentEditWindow_() { printStackTrace("needCurrentEditWindow"); return null; } /** Get current cell from current library */ public Cell getCurrentCell() { return curCellId != null ? database.getCell(curCellId) : null; } public Cell needCurrentCell() { Cell cell = getCurrentCell(); if (cell != null) return cell; throw new IllegalStateException("Can't get current Cell in database thread"); } public void repaintAllWindows() { printStackTrace("repaintAllWindows"); // Job.currentUI.repaintAllWindows(); } public void adjustReferencePoint(Cell cell, double cX, double cY) { // Job.currentUI.adjustReferencePoint(cell, cX, cY); }; public int getDefaultTextSize() { return 14; } public EditWindow_ displayCell(Cell cell) { throw new IllegalStateException(); } public void termLogging(final ErrorLogger logger, boolean explain, boolean terminate) { Client.fireServerEvent(new Client.TermLoggingEvent(logger, explain, terminate)); } public void updateNetworkErrors(Cell cell, List<ErrorLogger.MessageLog> errors) { throw new IllegalStateException(); } public void updateIncrementalDRCErrors(Cell cell, List<ErrorLogger.MessageLog> errors) { throw new IllegalStateException(); } /** * Method to return the error message associated with the current error. * Highlights associated graphics if "showhigh" is nonzero. */ public String reportLog(ErrorLogger.MessageLog log, boolean showhigh, boolean separateWindow, int position) { printStackTrace("reportLog"); // return the error message return log.getMessageString(); } /** * Method to show an error message. * @param message the error message to show. * @param title the title of a dialog with the error message. */ public void showErrorMessage(String message, String title) { Client.fireServerEvent(new Client.ShowMessageEvent(client, message, title, true)); } /** * Method to show an informational message. * @param message the message to show. * @param title the title of a dialog with the message. */ public void showInformationMessage(String message, String title) { Client.fireServerEvent(new Client.ShowMessageEvent(client, message, title, false)); } /** * Method print a message. * @param message the message to show. * @param newLine add new line after the message */ public void printMessage(String message, boolean newLine) { if (newLine) message += "\n"; Client.fireServerEvent(new Client.PrintEvent(client, message)); } /** * Method to start saving messages. * @param filePath file to save */ public void saveMessages(String filePath) { Client.fireServerEvent(new Client.SavePrintEvent(client, filePath)); } /** * Method to beep. */ public void beep() { Client.fireServerEvent(new Client.BeepEvent()); } /** * Method to show a message and ask for confirmation. * @param message the message to show. * @return true if "yes" was selected, false if "no" was selected. */ public boolean confirmMessage(Object message) { printStackTrace("confirmMessage"); return true; } /** * Method to ask for a choice among possibilities. * @param message the message to show. * @param title the title of the dialog with the query. * @param choices an array of choices to present, each in a button. * @param defaultChoice the default choice. * @return the index into the choices array that was selected. */ public int askForChoice(String message, String title, String [] choices, String defaultChoice) { throw new IllegalStateException(message); } /** * Method to ask for a line of text. * @param message the prompt message. * @param title the title of a dialog with the message. * @param def the default response. * @return the string (null if cancelled). */ public String askForInput(Object message, String title, String def) { throw new IllegalStateException(); } /** * Save current state of highlights and return its ID. */ public int saveHighlights() { return -1; } /** * Restore state of highlights by its ID. * @param highlightsId id of saved highlights. */ public void restoreHighlights(int highlightsId) {} /** * Show status of undo/redo buttons * @param newUndoEnabled new status of undo button. * @param newRedoEnabled new status of redo button. */ public void showUndoRedoStatus(boolean newUndoEnabled, boolean newRedoEnabled) {} } public static int getDefaultNumberOfThreads(){ return defaultNumThreads; } public static int getMaxNumberOfThreads(){ return maxNumberOfThreads; } }