Feature: improve the application exit-button process (#214)

* Improve the application exit-button behaviour
This commit is contained in:
Luis Uguina
2020-04-27 22:28:04 +10:00
committed by GitHub
parent aae5ec3254
commit d6984fa450
3 changed files with 277 additions and 226 deletions

2
.idea/gradle.xml generated
View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
@@ -11,7 +12,6 @@
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" /> <option name="resolveModulePerSourceSet" value="false" />
<option name="useAutoImport" value="true" />
<option name="useQualifiedModuleNames" value="true" /> <option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>

View File

@@ -135,247 +135,254 @@ public class Client {
Thread thread_sender = new Thread(runnable_sender); Thread thread_sender = new Thread(runnable_sender);
thread_sender.start(); thread_sender.start();
while (this.running == true) { do {
this.renderingJob = null; while (this.running == true) {
synchronized (this) { this.renderingJob = null;
if (this.suspended) { synchronized (this) {
this.gui.status("Client paused", true); 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 { while (this.suspended) {
Thread.sleep(wait); wait();
}
catch (InterruptedException e3) {
}
catch (IllegalArgumentException e3) {
this.log.error("Client::run sleepA failed " + e3);
} }
} }
this.gui.status("Requesting Job"); step = this.log.newCheckPoint();
this.renderingJob = this.server.requestJob(); try {
} Calendar next_request = this.nextJobRequest();
catch (FermeExceptionNoRightToRender e) { if (next_request != null) {
this.gui.error("User does not have enough right to render scene"); // wait
return -2; Date now = new Date();
} this.gui.status(String.format("Waiting until %tR before requesting job", next_request));
catch (FermeExceptionSessionDisabled e) { long wait = next_request.getTimeInMillis() - now.getTime();
this.gui.error(Error.humanString(Error.Type.SESSION_DISABLED)); if (wait < 0) {
// should wait forever to actually display the message to the user // it means the client has to wait until the next day
while (true) { wait += 24 * 3600 * 1000;
try { }
Thread.sleep(100000); try {
} Thread.sleep(wait);
catch (InterruptedException e1) { }
} catch (InterruptedException e3) {
}
} }
catch (FermeExceptionNoRendererAvailable e) { catch (IllegalArgumentException e3) {
this.gui.error(Error.humanString(Error.Type.RENDERER_NOT_AVAILABLE)); this.log.error("Client::run sleepA failed " + e3);
// should wait forever to actually display the message to the user
while (true) {
try {
Thread.sleep(100000);
}
catch (InterruptedException e1) {
}
}
}
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
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.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) {
try {
Thread.sleep(100000);
}
catch (InterruptedException e1) {
}
}
}
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 (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; this.renderingJob = null;
} }
} else {
} this.startTime = new Date().getTime(); // reset start session time because the server did it
catch (FermeServerDown e) { try {
int wait = ThreadLocalRandom.current().nextInt(10, 30 + 1); // max is exclusive Calendar next_request = this.nextJobRequest();
int time_sleep = 1000 * 60 * wait; if (next_request != null) {
this.gui.status(String.format("Can not connect to server. Please check your connectivity. Will retry in %s minutes", wait)); // wait
try { Date now = new Date();
Thread.sleep(time_sleep); this.gui.status(String.format("Waiting until %tR before requesting job", next_request));
} try {
catch (InterruptedException e1) { Thread.sleep(next_request.getTimeInMillis() - now.getTime());
return -3; }
} catch (InterruptedException e3) {
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);
}
catch (InterruptedException e1) {
return -3;
}
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);
}
catch (InterruptedException e1) {
return -3;
}
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);
}
catch (InterruptedException e1) {
return -3;
}
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 catch (IllegalArgumentException e3) {
int time_sleep = 1000 * 60 * wait; this.log.error("Client::run sleepB failed " + e3);
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; this.gui.status("Requesting Job");
int time_slept = 0; this.renderingJob = this.server.requestJob();
while (time_slept < time_sleep && this.running == true) { }
try { catch (FermeException e1) {
Thread.sleep(250); this.renderingJob = null;
}
} }
catch (InterruptedException e) { }
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 (InterruptedException e1) {
return -3; return -3;
} }
time_slept += 250; 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);
}
catch (InterruptedException e1) {
return -3;
}
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);
}
catch (InterruptedException e1) {
return -3;
}
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);
}
catch (InterruptedException e1) {
return -3;
}
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;
} }
this.suspended = false;
continue; // go back to ask job
}
this.log.debug("Got work to do id: " + this.renderingJob.getId() + " frame: " + this.renderingJob.getFrameNumber()); 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
}
ret = this.work(this.renderingJob); this.log.debug("Got work to do id: " + this.renderingJob.getId() + " frame: " + this.renderingJob.getFrameNumber());
if (ret == Error.Type.RENDERER_KILLED) {
this.log.removeCheckPoint(step);
continue;
}
if (ret == Error.Type.NO_SPACE_LEFT_ON_DEVICE) { ret = this.work(this.renderingJob);
Job frame_to_reset = this.renderingJob; // copy it because the sendError will take ~5min to execute if (ret == Error.Type.RENDERER_KILLED) {
this.renderingJob = null; this.log.removeCheckPoint(step);
this.gui.error(Error.humanString(ret)); continue;
this.sendError(step, frame_to_reset, ret); }
this.log.removeCheckPoint(step);
return -50;
}
if (ret != Error.Type.OK) { 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 Job frame_to_reset = this.renderingJob; // copy it because the sendError will take ~5min to execute
this.renderingJob = null; this.renderingJob = null;
this.gui.error(Error.humanString(ret)); this.gui.error(Error.humanString(ret));
this.sendError(step, frame_to_reset, ret); this.sendError(step, frame_to_reset, ret);
this.log.removeCheckPoint(step); this.log.removeCheckPoint(step);
continue; return -50;
} }
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) { if (ret != Error.Type.OK) {
gui.error("Client::run problem with confirmJob (returned " + ret + ")"); Job frame_to_reset = this.renderingJob; // copy it because the sendError will take ~5min to execute
sendError(step); this.renderingJob = null;
this.gui.error(Error.humanString(ret));
this.sendError(step, frame_to_reset, ret);
this.log.removeCheckPoint(step);
continue;
} }
}
else {
this.gui.status(String.format("Queuing frame for upload (%.2fMB)",
(this.renderingJob.getOutputImageSize() / 1024.0 / 1024.0)
));
this.jobsToValidate.add(this.renderingJob); 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)
));
this.uploadQueueSize++; ret = confirmJob(this.renderingJob);
this.uploadQueueVolume += this.renderingJob.getOutputImageSize(); if (ret != Error.Type.OK) {
this.gui.displayUploadQueueStats(uploadQueueSize, uploadQueueVolume); 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.renderingJob = null; 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);
} }
while (this.shouldWaitBeforeRender() == true) { // If we reach this point is bc the main loop (the one that controls all the workflow) has exited
try { // due to user requesting to exit the App and we are just waiting for the upload queue to empty
Thread.sleep(4000); // wait a little bit // If the user cancels the exit, then this.running will be true and the main loop will take
this.gui.status("Sending frames. Please wait"); // control again
}
catch (InterruptedException e3) {
}
}
this.log.removeCheckPoint(step);
}
// not running but maybe still sending frame
while (this.jobsToValidate.isEmpty() == false) {
try { try {
Thread.sleep(2300); // wait a little bit Thread.sleep(2300); // wait a little bit
this.gui.status("Uploading rendered frames before exiting. Please wait");
} }
catch (InterruptedException e3) { 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) { catch (Exception e1) {
// no exception should be raised in the actual launcher (applet or standalone) // no exception should be raised in the actual launcher (applet or standalone)
@@ -532,7 +539,6 @@ public class Client {
} }
/** /**
*
* @return the date of the next request, or null if there is not delay (null <=> now) * @return the date of the next request, or null if there is not delay (null <=> now)
*/ */
public Calendar nextJobRequest() { public Calendar nextJobRequest() {
@@ -615,7 +621,8 @@ public class Client {
} }
Observer removeSceneDirectoryOnceRenderHasStartedObserver = new Observer() { 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 // only remove the .blend since it's most important data
// and it's the only file we are sure will not be needed anymore // and it's the only file we are sure will not be needed anymore
scene_file.delete(); scene_file.delete();

View File

@@ -39,6 +39,7 @@ import javax.swing.BoxLayout;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.Spring; import javax.swing.Spring;
@@ -230,7 +231,7 @@ public class Working implements Activity {
JButton blockJob = new JButton("Block this project"); JButton blockJob = new JButton("Block this project");
blockJob.addActionListener(new blockJobAction()); blockJob.addActionListener(new blockJobAction());
exitAfterFrame = new JButton("Exit after this frame"); exitAfterFrame = new JButton("Exit");
exitAfterFrame.addActionListener(new ExitAfterAction()); exitAfterFrame.addActionListener(new ExitAfterAction());
buttonsPanel.add(settingsButton); buttonsPanel.add(settingsButton);
@@ -315,6 +316,22 @@ public class Working implements Activity {
(queueSize > 0 ? String.format(" (%.2fMB) ", (queueVolume / 1024.0 / 1024.0)) : ""), (queueSize > 0 ? String.format(" (%.2fMB) ", (queueVolume / 1024.0 / 1024.0)) : ""),
(queueSize == this.parent.getConfiguration().getMaxUploadingJob() ? "- Queue full!" : "") (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() { public void updateTime() {
@@ -475,11 +492,39 @@ public class Working implements Activity {
Client client = parent.getClient(); Client client = parent.getClient();
if (client != null) { if (client != null) {
if (client.isRunning()) { if (client.isRunning()) {
exitAfterFrame.setText("Cancel exit"); String[] exitJobOptions = {"Exit after current Jobs", "Exit Immediately", "Do Nothing"};
client.askForStop(); int jobsQueueSize = client.getUploadQueueSize() + (client.isRunning() ? 1 : 0);
int userDecision = JOptionPane.showOptionDialog(
null,
String.format("<html>You have <strong>%d frame%s</strong> 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 { else {
exitAfterFrame.setText("Exit after this frame"); exitAfterFrame.setText("Exit");
client.cancelStop(); client.cancelStop();
} }
} }
@@ -498,5 +543,4 @@ public class Working implements Activity {
} }
} }
} }
} }