From 4a0a326ce6964fd5bea21c7fbc368ecd5fcb324f Mon Sep 17 00:00:00 2001 From: Luis Uguina Date: Mon, 27 Apr 2020 22:28:04 +1000 Subject: [PATCH] Feature: improve the application exit-button process (#214) * Improve the application exit-button behaviour --- .idea/gradle.xml | 2 +- src/com/sheepit/client/Client.java | 411 +++++++++--------- .../standalone/swing/activity/Working.java | 54 ++- 3 files changed, 259 insertions(+), 208 deletions(-) diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 310dbb80..8da083f2 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,5 +1,6 @@ + diff --git a/src/com/sheepit/client/Client.java b/src/com/sheepit/client/Client.java index 4329555e..15259e62 100644 --- a/src/com/sheepit/client/Client.java +++ b/src/com/sheepit/client/Client.java @@ -135,247 +135,254 @@ public void run() { Thread thread_sender = new Thread(runnable_sender); thread_sender.start(); - while (this.running == true) { - this.renderingJob = null; - synchronized (this) { - if (this.suspended) { - this.gui.status("Client paused", true); + do { + while (this.running == true) { + this.renderingJob = null; + synchronized (this) { + if (this.suspended) { + this.gui.status("Client paused", true); + } + while (this.suspended) { + wait(); + } + } + step = this.log.newCheckPoint(); + try { + Calendar next_request = this.nextJobRequest(); + if (next_request != null) { + // wait + Date now = new Date(); + this.gui.status(String.format("Waiting until %tR before requesting job", next_request)); + long wait = next_request.getTimeInMillis() - now.getTime(); + if (wait < 0) { + // it means the client has to wait until the next day + wait += 24 * 3600 * 1000; + } + try { + Thread.sleep(wait); + } + catch (InterruptedException e3) { + + } + catch (IllegalArgumentException e3) { + this.log.error("Client::run sleepA failed " + e3); + } + } + this.gui.status("Requesting Job"); + this.renderingJob = this.server.requestJob(); } - while (this.suspended) { - wait(); + catch (FermeExceptionNoRightToRender e) { + this.gui.error("User does not have enough right to render scene"); + return -2; } - } - step = this.log.newCheckPoint(); - try { - Calendar next_request = this.nextJobRequest(); - if (next_request != null) { - // wait - Date now = new Date(); - this.gui.status(String.format("Waiting until %tR before requesting job", next_request)); - long wait = next_request.getTimeInMillis() - now.getTime(); - if (wait < 0) { - // it means the client has to wait until the next day - wait += 24*3600*1000; + catch (FermeExceptionSessionDisabled e) { + this.gui.error(Error.humanString(Error.Type.SESSION_DISABLED)); + // should wait forever to actually display the message to the user + while (true) { + try { + Thread.sleep(100000); + } + catch (InterruptedException e1) { + } } - try { - Thread.sleep(wait); + } + catch (FermeExceptionNoRendererAvailable e) { + this.gui.error(Error.humanString(Error.Type.RENDERER_NOT_AVAILABLE)); + // should wait forever to actually display the message to the user + while (true) { + try { + Thread.sleep(100000); + } + catch (InterruptedException e1) { + } } - catch (InterruptedException e3) { - + } + catch (FermeExceptionNoSession e) { + this.log.debug("User has no session need to re-authenticate"); + ret = this.server.getConfiguration(); + if (ret != Error.Type.OK) { + this.renderingJob = null; } - catch (IllegalArgumentException e3) { - this.log.error("Client::run sleepA failed " + e3); + else { + this.startTime = new Date().getTime(); // reset start session time because the server did it + try { + Calendar next_request = this.nextJobRequest(); + if (next_request != null) { + // wait + Date now = new Date(); + this.gui.status(String.format("Waiting until %tR before requesting job", next_request)); + try { + Thread.sleep(next_request.getTimeInMillis() - now.getTime()); + } + catch (InterruptedException e3) { + + } + catch (IllegalArgumentException e3) { + this.log.error("Client::run sleepB failed " + e3); + } + } + this.gui.status("Requesting Job"); + this.renderingJob = this.server.requestJob(); + } + catch (FermeException e1) { + this.renderingJob = null; + } } } - this.gui.status("Requesting Job"); - this.renderingJob = this.server.requestJob(); - } - catch (FermeExceptionNoRightToRender e) { - this.gui.error("User does not have enough right to render scene"); - return -2; - } - catch (FermeExceptionSessionDisabled e) { - this.gui.error(Error.humanString(Error.Type.SESSION_DISABLED)); - // should wait forever to actually display the message to the user - while (true) { + catch (FermeServerDown e) { + int wait = ThreadLocalRandom.current().nextInt(10, 30 + 1); // max is exclusive + int time_sleep = 1000 * 60 * wait; + this.gui.status(String.format("Can not connect to server. Please check your connectivity. Will retry in %s minutes", wait)); try { - Thread.sleep(100000); + Thread.sleep(time_sleep); } catch (InterruptedException e1) { + return -3; } + continue; // go back to ask job } - } - catch (FermeExceptionNoRendererAvailable e) { - this.gui.error(Error.humanString(Error.Type.RENDERER_NOT_AVAILABLE)); - // should wait forever to actually display the message to the user - while (true) { + catch (FermeExceptionServerOverloaded e) { + int wait = ThreadLocalRandom.current().nextInt(10, 30 + 1); // max is exclusive + int time_sleep = 1000 * 60 * wait; + this.gui.status(String.format("Server is overloaded and cannot give frame to render. Will retry in %s minutes", wait)); try { - Thread.sleep(100000); + Thread.sleep(time_sleep); } catch (InterruptedException e1) { + return -3; } + continue; // go back to ask job } - } - catch (FermeExceptionNoSession e) { - this.log.debug("User has no session need to re-authenticate"); - ret = this.server.getConfiguration(); - if (ret != Error.Type.OK) { - this.renderingJob = null; - } - else { - this.startTime = new Date().getTime(); // reset start session time because the server did it + catch (FermeExceptionServerInMaintenance e) { + int wait = ThreadLocalRandom.current().nextInt(20, 30 + 1); // max is exclusive + int time_sleep = 1000 * 60 * wait; + this.gui.status(String.format("Server is in maintenance and cannot give frame to render. Will retry in %s minutes", wait)); try { - Calendar next_request = this.nextJobRequest(); - if (next_request != null) { - // wait - Date now = new Date(); - this.gui.status(String.format("Waiting until %tR before requesting job", next_request)); - try { - Thread.sleep(next_request.getTimeInMillis() - now.getTime()); - } - catch (InterruptedException e3) { - - } - catch (IllegalArgumentException e3) { - this.log.error("Client::run sleepB failed " + e3); - } - } - this.gui.status("Requesting Job"); - this.renderingJob = this.server.requestJob(); + Thread.sleep(time_sleep); } - catch (FermeException e1) { - this.renderingJob = null; + catch (InterruptedException e1) { + return -3; } + continue; // go back to ask job } - } - catch (FermeServerDown e) { - int wait = ThreadLocalRandom.current().nextInt(10, 30 + 1); // max is exclusive - int time_sleep = 1000 * 60 * wait; - this.gui.status(String.format("Can not connect to server. Please check your connectivity. Will retry in %s minutes", wait)); - try { - Thread.sleep(time_sleep); + catch (FermeExceptionBadResponseFromServer e) { + int wait = ThreadLocalRandom.current().nextInt(15, 30 + 1); // max is exclusive + int time_sleep = 1000 * 60 * wait; + this.gui.status(String.format("Bad answer from server. Will retry in %s minutes", wait)); + try { + Thread.sleep(time_sleep); + } + catch (InterruptedException e1) { + return -3; + } + continue; // go back to ask job } - catch (InterruptedException e1) { - return -3; + catch (FermeException e) { + this.gui.error("Client::run exception requestJob (1) " + e.getMessage()); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + this.log.debug("Client::run exception " + e + " stacktrace: " + sw.toString()); + this.sendError(step); + continue; } - continue; // go back to ask job - } - catch (FermeExceptionServerOverloaded e) { - int wait = ThreadLocalRandom.current().nextInt(10, 30 + 1); // max is exclusive - int time_sleep = 1000 * 60 * wait; - this.gui.status(String.format("Server is overloaded and cannot give frame to render. Will retry in %s minutes", wait)); - try { - Thread.sleep(time_sleep); + + if (this.renderingJob == null) { // no job + int wait = ThreadLocalRandom.current().nextInt(10, 30 + 1); // max is exclusive + int time_sleep = 1000 * 60 * wait; + Date wakeup_time = new Date(new Date().getTime() + time_sleep); + this.gui.status(String.format("No job available. Sleeping for %d minutes (will wake up at %tR)", wait, wakeup_time)); + this.suspended = true; + int time_slept = 0; + while (time_slept < time_sleep && this.running == true) { + try { + Thread.sleep(250); + } + catch (InterruptedException e) { + return -3; + } + time_slept += 250; + } + this.suspended = false; + continue; // go back to ask job } - catch (InterruptedException e1) { - return -3; + + this.log.debug("Got work to do id: " + this.renderingJob.getId() + " frame: " + this.renderingJob.getFrameNumber()); + + ret = this.work(this.renderingJob); + if (ret == Error.Type.RENDERER_KILLED) { + this.log.removeCheckPoint(step); + continue; } - continue; // go back to ask job - } - catch (FermeExceptionServerInMaintenance e) { - int wait = ThreadLocalRandom.current().nextInt(20, 30 + 1); // max is exclusive - int time_sleep = 1000 * 60 * wait; - this.gui.status(String.format("Server is in maintenance and cannot give frame to render. Will retry in %s minutes", wait)); - try { - Thread.sleep(time_sleep); + + if (ret == Error.Type.NO_SPACE_LEFT_ON_DEVICE) { + Job frame_to_reset = this.renderingJob; // copy it because the sendError will take ~5min to execute + this.renderingJob = null; + this.gui.error(Error.humanString(ret)); + this.sendError(step, frame_to_reset, ret); + this.log.removeCheckPoint(step); + return -50; } - catch (InterruptedException e1) { - return -3; + + if (ret != Error.Type.OK) { + Job frame_to_reset = this.renderingJob; // copy it because the sendError will take ~5min to execute + this.renderingJob = null; + this.gui.error(Error.humanString(ret)); + this.sendError(step, frame_to_reset, ret); + this.log.removeCheckPoint(step); + continue; } - continue; // go back to ask job - } - catch (FermeExceptionBadResponseFromServer e) { - int wait = ThreadLocalRandom.current().nextInt(15, 30 + 1); // max is exclusive - int time_sleep = 1000 * 60 * wait; - this.gui.status(String.format("Bad answer from server. Will retry in %s minutes", wait)); - try { - Thread.sleep(time_sleep); + + if (this.renderingJob.isSynchronousUpload() == true) { // power or compute_method job, need to upload right away + this.gui.status(String.format("Uploading frame (%.2fMB)", + (this.renderingJob.getOutputImageSize() / 1024.0 / 1024.0) + )); + + ret = confirmJob(this.renderingJob); + if (ret != Error.Type.OK) { + gui.error("Client::run problem with confirmJob (returned " + ret + ")"); + sendError(step); + } } - catch (InterruptedException e1) { - return -3; + else { + this.gui.status(String.format("Queuing frame for upload (%.2fMB)", + (this.renderingJob.getOutputImageSize() / 1024.0 / 1024.0) + )); + + this.jobsToValidate.add(this.renderingJob); + + this.uploadQueueSize++; + this.uploadQueueVolume += this.renderingJob.getOutputImageSize(); + this.gui.displayUploadQueueStats(uploadQueueSize, uploadQueueVolume); + + this.renderingJob = null; } - continue; // go back to ask job - } - catch (FermeException e) { - this.gui.error("Client::run exception requestJob (1) " + e.getMessage()); - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - e.printStackTrace(pw); - this.log.debug("Client::run exception " + e + " stacktrace: " + sw.toString()); - this.sendError(step); - continue; - } - - if (this.renderingJob == null) { // no job - int wait = ThreadLocalRandom.current().nextInt(10, 30 + 1); // max is exclusive - int time_sleep = 1000 * 60 * wait; - Date wakeup_time = new Date(new Date().getTime() + time_sleep); - this.gui.status(String.format("No job available. Sleeping for %d minutes (will wake up at %tR)", wait, wakeup_time)); - this.suspended = true; - int time_slept = 0; - while (time_slept < time_sleep && this.running == true) { + + while (this.shouldWaitBeforeRender() == true) { try { - Thread.sleep(250); + Thread.sleep(4000); // wait a little bit + this.gui.status("Sending frames. Please wait"); } - catch (InterruptedException e) { - return -3; + catch (InterruptedException e3) { } - time_slept += 250; } - this.suspended = false; - continue; // go back to ask job - } - - this.log.debug("Got work to do id: " + this.renderingJob.getId() + " frame: " + this.renderingJob.getFrameNumber()); - - ret = this.work(this.renderingJob); - if (ret == Error.Type.RENDERER_KILLED) { - this.log.removeCheckPoint(step); - continue; - } - - if (ret == Error.Type.NO_SPACE_LEFT_ON_DEVICE) { - Job frame_to_reset = this.renderingJob; // copy it because the sendError will take ~5min to execute - this.renderingJob = null; - this.gui.error(Error.humanString(ret)); - this.sendError(step, frame_to_reset, ret); - this.log.removeCheckPoint(step); - return -50; - } - - if (ret != Error.Type.OK) { - Job frame_to_reset = this.renderingJob; // copy it because the sendError will take ~5min to execute - this.renderingJob = null; - this.gui.error(Error.humanString(ret)); - this.sendError(step, frame_to_reset, ret); this.log.removeCheckPoint(step); - continue; } - if (this.renderingJob.isSynchronousUpload() == true) { // power or compute_method job, need to upload right away - this.gui.status(String.format("Uploading frame (%.2fMB)", - (this.renderingJob.getOutputImageSize() / 1024.0 / 1024.0) - )); - - ret = confirmJob(this.renderingJob); - if (ret != Error.Type.OK) { - gui.error("Client::run problem with confirmJob (returned " + ret + ")"); - sendError(step); - } - } - else { - this.gui.status(String.format("Queuing frame for upload (%.2fMB)", - (this.renderingJob.getOutputImageSize() / 1024.0 / 1024.0) - )); - - this.jobsToValidate.add(this.renderingJob); - - this.uploadQueueSize++; - this.uploadQueueVolume += this.renderingJob.getOutputImageSize(); - this.gui.displayUploadQueueStats(uploadQueueSize, uploadQueueVolume); - - this.renderingJob = null; - } - - while (this.shouldWaitBeforeRender() == true) { - try { - Thread.sleep(4000); // wait a little bit - this.gui.status("Sending frames. Please wait"); - } - catch (InterruptedException e3) { - } - } - this.log.removeCheckPoint(step); - } - - // not running but maybe still sending frame - while (this.jobsToValidate.isEmpty() == false) { + // If we reach this point is bc the main loop (the one that controls all the workflow) has exited + // due to user requesting to exit the App and we are just waiting for the upload queue to empty + // If the user cancels the exit, then this.running will be true and the main loop will take + // control again try { Thread.sleep(2300); // wait a little bit + this.gui.status("Uploading rendered frames before exiting. Please wait"); } catch (InterruptedException e3) { } - } + + // This loop will remain valid until all the background uploads have + // finished (unless the stop() method has been triggered) + } while (this.uploadQueueSize > 0); } catch (Exception e1) { // no exception should be raised in the actual launcher (applet or standalone) @@ -532,7 +539,6 @@ protected void sendError(int step_, Job job_to_reset_, Error.Type error) { } /** - * * @return the date of the next request, or null if there is not delay (null <=> now) */ public Calendar nextJobRequest() { @@ -615,7 +621,8 @@ public Error.Type work(final Job ajob) { } Observer removeSceneDirectoryOnceRenderHasStartedObserver = new Observer() { - @Override public void update(Observable observable, Object o) { + @Override + public void update(Observable observable, Object o) { // only remove the .blend since it's most important data // and it's the only file we are sure will not be needed anymore scene_file.delete(); diff --git a/src/com/sheepit/client/standalone/swing/activity/Working.java b/src/com/sheepit/client/standalone/swing/activity/Working.java index 94a2a35d..b4343cd8 100644 --- a/src/com/sheepit/client/standalone/swing/activity/Working.java +++ b/src/com/sheepit/client/standalone/swing/activity/Working.java @@ -39,6 +39,7 @@ import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.UIManager; import javax.swing.Spring; @@ -230,7 +231,7 @@ public void show() { JButton blockJob = new JButton("Block this project"); blockJob.addActionListener(new blockJobAction()); - exitAfterFrame = new JButton("Exit after this frame"); + exitAfterFrame = new JButton("Exit"); exitAfterFrame.addActionListener(new ExitAfterAction()); buttonsPanel.add(settingsButton); @@ -315,6 +316,22 @@ public void displayUploadQueueStats(int queueSize, long queueVolume) { (queueSize > 0 ? String.format(" (%.2fMB) ", (queueVolume / 1024.0 / 1024.0)) : ""), (queueSize == this.parent.getConfiguration().getMaxUploadingJob() ? "- Queue full!" : "") )); + + // If the user has requested to exit, then we need to update the JButton with the queue size + if (this.exitAfterFrame.getText().startsWith("Cancel")) { + Client client = parent.getClient(); + + if (client != null) { + if (client.isRunning()) { + queueSize++; + } + } + + exitAfterFrame.setText(String.format("Cancel exit (%s frame%s to go)", + queueSize, + (queueSize > 1 ? "s" : "")) + ); + } } public void updateTime() { @@ -475,11 +492,39 @@ public void actionPerformed(ActionEvent e) { Client client = parent.getClient(); if (client != null) { if (client.isRunning()) { - exitAfterFrame.setText("Cancel exit"); - client.askForStop(); + String[] exitJobOptions = {"Exit after current Jobs", "Exit Immediately", "Do Nothing"}; + int jobsQueueSize = client.getUploadQueueSize() + (client.isRunning() ? 1 : 0); + + int userDecision = JOptionPane.showOptionDialog( + null, + String.format("You have %d frame%s being uploaded or rendered. Do you want to finish the jobs or exit now?.\n\n", + jobsQueueSize , // Add the current frame to the total count ONLY if the client is running + (jobsQueueSize > 1 ? "s" : ""), + (jobsQueueSize > 1 ? (jobsQueueSize + " ") : ""), + (jobsQueueSize > 1 ? "s" : "") + ), + "Exit Now or Later", + JOptionPane.DEFAULT_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + exitJobOptions, + exitJobOptions[2]); // Make the "Do nothing" button the default one to avoid mistakes + + if (userDecision == 0) { + exitAfterFrame.setText(String.format("Cancel exit (%s frame%s to go)", + jobsQueueSize, + (jobsQueueSize > 1 ? "s" : "")) + ); + + client.askForStop(); + } + else if (userDecision == 1) { + client.stop(); + System.exit(0); + } } else { - exitAfterFrame.setText("Exit after this frame"); + exitAfterFrame.setText("Exit"); client.cancelStop(); } } @@ -498,5 +543,4 @@ public void actionPerformed(ActionEvent e) { } } } - }