2014-11-20 13:21:19 +00:00
|
|
|
/*
|
|
|
|
|
* Copyright (C) 2010-2014 Laurent CLOUET
|
|
|
|
|
* Author Laurent CLOUET <laurent.clouet@nopnop.net>
|
|
|
|
|
*
|
2020-05-28 13:28:42 +02:00
|
|
|
* This program is free software; you can redistribute it and/or
|
2014-11-20 13:21:19 +00:00
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
|
* as published by the Free Software Foundation; version 2
|
|
|
|
|
* of the License.
|
|
|
|
|
*
|
|
|
|
|
* This program 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 this program; if not, write to the Free Software
|
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package com.sheepit.client;
|
|
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.io.PrintWriter;
|
|
|
|
|
import java.io.StringWriter;
|
2022-02-27 20:46:58 +01:00
|
|
|
import java.nio.file.FileSystemException;
|
2020-10-21 22:03:09 +11:00
|
|
|
import java.nio.file.Files;
|
2022-02-18 02:20:57 +01:00
|
|
|
import java.nio.file.Path;
|
2020-10-21 22:03:09 +11:00
|
|
|
import java.nio.file.Paths;
|
|
|
|
|
import java.nio.file.StandardCopyOption;
|
2014-11-20 13:21:19 +00:00
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Calendar;
|
|
|
|
|
import java.util.Date;
|
2021-12-21 13:50:28 +00:00
|
|
|
import java.util.Locale;
|
2019-08-07 18:40:02 +02:00
|
|
|
import java.util.Observable;
|
|
|
|
|
import java.util.Observer;
|
2020-10-27 14:32:11 +01:00
|
|
|
import java.util.Optional;
|
2020-10-21 22:03:09 +11:00
|
|
|
import java.util.Random;
|
2020-07-28 00:49:36 +10:00
|
|
|
import java.util.Timer;
|
|
|
|
|
import java.util.TimerTask;
|
2014-11-20 13:21:19 +00:00
|
|
|
import java.util.concurrent.ArrayBlockingQueue;
|
|
|
|
|
import java.util.concurrent.BlockingQueue;
|
2018-03-24 20:25:18 +01:00
|
|
|
import java.util.concurrent.ThreadLocalRandom;
|
2020-10-21 22:03:09 +11:00
|
|
|
import java.util.concurrent.TimeUnit;
|
2014-11-20 13:21:19 +00:00
|
|
|
|
|
|
|
|
import com.sheepit.client.Error.ServerCode;
|
|
|
|
|
import com.sheepit.client.Error.Type;
|
|
|
|
|
import com.sheepit.client.exception.FermeException;
|
2016-09-11 13:46:12 +02:00
|
|
|
import com.sheepit.client.exception.FermeExceptionBadResponseFromServer;
|
2017-06-06 13:38:48 +02:00
|
|
|
import com.sheepit.client.exception.FermeExceptionNoRendererAvailable;
|
2014-11-20 13:21:19 +00:00
|
|
|
import com.sheepit.client.exception.FermeExceptionNoRightToRender;
|
|
|
|
|
import com.sheepit.client.exception.FermeExceptionNoSession;
|
2016-05-02 13:26:11 +02:00
|
|
|
import com.sheepit.client.exception.FermeExceptionNoSpaceLeftOnDevice;
|
2022-03-11 14:02:22 +00:00
|
|
|
import com.sheepit.client.exception.FermeExceptionPathInvalid;
|
|
|
|
|
import com.sheepit.client.exception.FermeExceptionNoWritePermission;
|
2016-03-16 14:07:42 +01:00
|
|
|
import com.sheepit.client.exception.FermeExceptionServerInMaintenance;
|
|
|
|
|
import com.sheepit.client.exception.FermeExceptionServerOverloaded;
|
2014-11-20 13:21:19 +00:00
|
|
|
import com.sheepit.client.exception.FermeExceptionSessionDisabled;
|
2015-07-08 19:44:38 +01:00
|
|
|
import com.sheepit.client.exception.FermeServerDown;
|
2020-06-03 00:06:03 +10:00
|
|
|
import com.sheepit.client.hardware.cpu.CPU;
|
2014-11-20 13:21:19 +00:00
|
|
|
import com.sheepit.client.os.OS;
|
|
|
|
|
|
2020-06-18 21:57:52 +10:00
|
|
|
import lombok.AllArgsConstructor;
|
2019-08-07 22:17:59 +02:00
|
|
|
import lombok.Data;
|
2021-09-09 21:29:00 +02:00
|
|
|
import okhttp3.HttpUrl;
|
2019-08-07 22:17:59 +02:00
|
|
|
|
2020-05-28 13:28:42 +02:00
|
|
|
@Data public class Client {
|
2021-12-21 10:02:32 +00:00
|
|
|
public static final int MIN_JOB_ID = 20; //to distinguish between actual jobs and test frames
|
2021-12-21 13:50:28 +00:00
|
|
|
private static final Locale LOCALE = Locale.ENGLISH;
|
2014-11-20 13:21:19 +00:00
|
|
|
private Gui gui;
|
|
|
|
|
private Server server;
|
2019-08-07 22:17:59 +02:00
|
|
|
private Configuration configuration;
|
2014-11-20 13:21:19 +00:00
|
|
|
private Log log;
|
|
|
|
|
private Job renderingJob;
|
2016-10-31 15:27:20 +01:00
|
|
|
private Job previousJob;
|
2020-06-18 21:57:52 +10:00
|
|
|
private BlockingQueue<QueuedJob> jobsToValidate;
|
2014-11-20 13:21:19 +00:00
|
|
|
private boolean isValidatingJob;
|
2019-08-07 22:17:59 +02:00
|
|
|
private long startTime;
|
2021-07-16 23:37:24 +00:00
|
|
|
private boolean sessionStarted;
|
2014-11-20 13:21:19 +00:00
|
|
|
|
|
|
|
|
private boolean disableErrorSending;
|
|
|
|
|
private boolean running;
|
2014-12-02 20:28:03 +00:00
|
|
|
private boolean suspended;
|
2020-07-28 00:49:36 +10:00
|
|
|
private boolean shuttingdown;
|
2014-11-20 13:21:19 +00:00
|
|
|
|
2015-05-05 18:35:25 +02:00
|
|
|
private int maxDownloadFileAttempts = 5;
|
|
|
|
|
|
2020-04-20 01:01:43 +10:00
|
|
|
private int uploadQueueSize;
|
|
|
|
|
private long uploadQueueVolume;
|
2020-05-20 22:32:39 +10:00
|
|
|
private int noJobRetryIter;
|
2020-04-20 01:01:43 +10:00
|
|
|
|
2019-08-07 22:17:59 +02:00
|
|
|
public Client(Gui gui_, Configuration configuration, String url_) {
|
|
|
|
|
this.configuration = configuration;
|
|
|
|
|
this.server = new Server(url_, this.configuration, this);
|
|
|
|
|
this.log = Log.getInstance(this.configuration);
|
2014-11-20 13:21:19 +00:00
|
|
|
this.gui = gui_;
|
|
|
|
|
this.renderingJob = null;
|
2016-10-31 15:27:20 +01:00
|
|
|
this.previousJob = null;
|
2020-06-18 21:57:52 +10:00
|
|
|
this.jobsToValidate = new ArrayBlockingQueue<QueuedJob>(5);
|
2014-11-20 13:21:19 +00:00
|
|
|
this.isValidatingJob = false;
|
|
|
|
|
|
|
|
|
|
this.disableErrorSending = false;
|
2016-02-08 13:06:33 +01:00
|
|
|
this.running = false;
|
2014-12-02 20:28:03 +00:00
|
|
|
this.suspended = false;
|
2020-07-28 00:49:36 +10:00
|
|
|
this.shuttingdown = false;
|
2020-04-20 01:01:43 +10:00
|
|
|
|
|
|
|
|
this.uploadQueueSize = 0;
|
|
|
|
|
this.uploadQueueVolume = 0;
|
2020-05-20 22:32:39 +10:00
|
|
|
this.noJobRetryIter = 0;
|
2021-07-16 23:37:24 +00:00
|
|
|
|
|
|
|
|
this.sessionStarted = false;
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String toString() {
|
2019-08-07 22:17:59 +02:00
|
|
|
return String.format("Client (configuration %s, server %s)", this.configuration, this.server);
|
2016-10-12 00:34:51 +02:00
|
|
|
}
|
|
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
public int run() {
|
2019-08-07 22:17:59 +02:00
|
|
|
if (this.configuration.checkOSisSupported() == false) {
|
2014-12-17 21:47:31 +00:00
|
|
|
this.gui.error(Error.humanString(Error.Type.OS_NOT_SUPPORTED));
|
|
|
|
|
return -3;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-07 22:17:59 +02:00
|
|
|
if (this.configuration.checkCPUisSupported() == false) {
|
2014-12-17 21:47:31 +00:00
|
|
|
this.gui.error(Error.humanString(Error.Type.CPU_NOT_SUPPORTED));
|
|
|
|
|
return -4;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-08 13:06:33 +01:00
|
|
|
this.running = true;
|
|
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
int step;
|
|
|
|
|
try {
|
|
|
|
|
step = this.log.newCheckPoint();
|
|
|
|
|
this.gui.status("Starting");
|
|
|
|
|
|
|
|
|
|
Error.Type ret;
|
|
|
|
|
ret = this.server.getConfiguration();
|
|
|
|
|
|
|
|
|
|
if (ret != Error.Type.OK) {
|
2014-12-16 23:02:14 +00:00
|
|
|
this.gui.error(Error.humanString(ret));
|
2014-11-20 13:21:19 +00:00
|
|
|
if (ret != Error.Type.AUTHENTICATION_FAILED) {
|
|
|
|
|
Log.printCheckPoint(step);
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-28 00:49:36 +10:00
|
|
|
// If the user has selected to shutdown the computer at any specific time, set a scheduled task
|
|
|
|
|
if (configuration.getShutdownTime() > 0) {
|
|
|
|
|
new Timer().schedule(new TimerTask() {
|
|
|
|
|
@Override public void run() {
|
|
|
|
|
shuttingdown = true;
|
|
|
|
|
log.debug("Initiating the computer's shutting down process");
|
|
|
|
|
|
|
|
|
|
if (configuration.getShutdownMode().equals("wait")) {
|
|
|
|
|
// Soft stop. Complete current render (if any), finish uploading frames and then shutdown the computer
|
|
|
|
|
askForStop();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Soft stop. Interrupt the current render, abort pending uploads, close the client and shutdown the computer
|
|
|
|
|
stop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, this.configuration.getShutdownTime());
|
|
|
|
|
}
|
2022-03-13 12:26:11 +00:00
|
|
|
|
|
|
|
|
// Check integrity of all files in the working directories
|
|
|
|
|
this.configuration.cleanWorkingDirectory();
|
|
|
|
|
|
2019-08-07 22:17:59 +02:00
|
|
|
this.startTime = new Date().getTime();
|
2014-11-20 13:21:19 +00:00
|
|
|
this.server.start(); // for staying alive
|
|
|
|
|
|
2014-12-23 20:05:29 +01:00
|
|
|
// create a thread which will send the frame
|
2014-11-20 13:21:19 +00:00
|
|
|
Runnable runnable_sender = new Runnable() {
|
|
|
|
|
public void run() {
|
|
|
|
|
senderLoop();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
Thread thread_sender = new Thread(runnable_sender);
|
|
|
|
|
thread_sender.start();
|
|
|
|
|
|
2020-04-27 22:28:04 +10:00
|
|
|
do {
|
2021-09-10 11:42:33 +02:00
|
|
|
while (this.running) {
|
2020-04-27 22:28:04 +10:00
|
|
|
this.renderingJob = null;
|
2020-07-28 00:49:36 +10:00
|
|
|
|
2020-04-27 22:28:04 +10:00
|
|
|
synchronized (this) {
|
|
|
|
|
if (this.suspended) {
|
|
|
|
|
this.gui.status("Client paused", true);
|
|
|
|
|
}
|
2020-07-28 00:49:36 +10:00
|
|
|
while (this.suspended && !this.shuttingdown) {
|
2020-04-27 22:28:04 +10:00
|
|
|
wait();
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-28 00:49:36 +10:00
|
|
|
|
2020-04-27 22:28:04 +10:00
|
|
|
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) {
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2020-04-27 22:28:04 +10:00
|
|
|
}
|
|
|
|
|
catch (IllegalArgumentException e3) {
|
|
|
|
|
this.log.error("Client::run sleepA failed " + e3);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.gui.status("Requesting Job");
|
|
|
|
|
this.renderingJob = this.server.requestJob();
|
2020-04-26 23:35:05 +10:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
catch (FermeExceptionNoRightToRender e) {
|
|
|
|
|
this.gui.error("User does not have enough right to render scene");
|
|
|
|
|
return -2;
|
2014-12-02 20:28:03 +00:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
catch (FermeExceptionSessionDisabled e) {
|
|
|
|
|
this.gui.error(Error.humanString(Error.Type.SESSION_DISABLED));
|
|
|
|
|
// should wait forever to actually display the message to the user
|
2021-10-02 19:56:54 +00:00
|
|
|
while (shuttingdown == false) {
|
2020-04-27 22:28:04 +10:00
|
|
|
try {
|
2020-07-28 00:49:36 +10:00
|
|
|
Thread.sleep(1000);
|
2020-04-27 22:28:04 +10:00
|
|
|
}
|
|
|
|
|
catch (InterruptedException e1) {
|
|
|
|
|
}
|
2019-03-06 21:12:08 +01:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
}
|
|
|
|
|
catch (FermeExceptionNoRendererAvailable e) {
|
|
|
|
|
this.gui.error(Error.humanString(Error.Type.RENDERER_NOT_AVAILABLE));
|
|
|
|
|
// should wait forever to actually display the message to the user
|
2021-10-02 19:56:54 +00:00
|
|
|
while (shuttingdown == false) {
|
2020-04-27 22:28:04 +10:00
|
|
|
try {
|
2020-07-28 00:49:36 +10:00
|
|
|
Thread.sleep(1000);
|
2020-04-27 22:28:04 +10:00
|
|
|
}
|
|
|
|
|
catch (InterruptedException e1) {
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
}
|
|
|
|
|
catch (FermeExceptionNoSession e) {
|
2020-05-16 18:42:16 +10:00
|
|
|
this.log.debug("User has no session and needs to re-authenticate");
|
2020-04-27 22:28:04 +10:00
|
|
|
ret = this.server.getConfiguration();
|
|
|
|
|
if (ret != Error.Type.OK) {
|
|
|
|
|
this.renderingJob = null;
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
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));
|
2020-07-28 00:49:36 +10:00
|
|
|
long timeToSleep = next_request.getTimeInMillis() - now.getTime();
|
2020-04-27 22:28:04 +10:00
|
|
|
try {
|
2020-07-28 00:49:36 +10:00
|
|
|
int timeSlept = 0;
|
|
|
|
|
while (timeSlept < timeToSleep && this.running && !this.shuttingdown) {
|
|
|
|
|
Thread.sleep(1000);
|
|
|
|
|
timeSlept += 1000;
|
|
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
}
|
|
|
|
|
catch (InterruptedException e3) {
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2020-04-27 22:28:04 +10:00
|
|
|
}
|
|
|
|
|
catch (IllegalArgumentException e3) {
|
|
|
|
|
this.log.error("Client::run sleepB failed " + e3);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-28 00:49:36 +10:00
|
|
|
|
|
|
|
|
// if we have broken the wait loop because a stop or shutdown signal, go back to the main loop to exit
|
|
|
|
|
if (!this.running || this.shuttingdown) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-27 22:28:04 +10:00
|
|
|
this.gui.status("Requesting Job");
|
|
|
|
|
this.renderingJob = this.server.requestJob();
|
|
|
|
|
}
|
|
|
|
|
catch (FermeException e1) {
|
|
|
|
|
this.renderingJob = null;
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
catch (FermeServerDown e) {
|
|
|
|
|
int wait = ThreadLocalRandom.current().nextInt(10, 30 + 1); // max is exclusive
|
|
|
|
|
int time_sleep = 1000 * 60 * wait;
|
2020-05-16 18:42:16 +10:00
|
|
|
this.gui.status(String.format("Cannot connect to the server. Please check your connectivity. Will try again at %tR",
|
|
|
|
|
new Date(new Date().getTime() + time_sleep)));
|
2014-11-20 13:21:19 +00:00
|
|
|
try {
|
2020-07-28 00:49:36 +10:00
|
|
|
int timeSlept = 0;
|
|
|
|
|
while (timeSlept < time_sleep && this.running && !this.shuttingdown) {
|
|
|
|
|
Thread.sleep(1000);
|
|
|
|
|
timeSlept += 1000;
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
catch (InterruptedException e1) {
|
2020-04-27 22:28:04 +10:00
|
|
|
return -3;
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2020-06-16 22:03:17 +10:00
|
|
|
this.log.removeCheckPoint(step);
|
2020-04-27 22:28:04 +10:00
|
|
|
continue; // go back to ask job
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
catch (FermeExceptionServerOverloaded e) {
|
|
|
|
|
int wait = ThreadLocalRandom.current().nextInt(10, 30 + 1); // max is exclusive
|
|
|
|
|
int time_sleep = 1000 * 60 * wait;
|
2020-05-16 18:42:16 +10:00
|
|
|
this.gui.status(String.format("The server is overloaded and cannot allocate a job. Will try again at %tR",
|
|
|
|
|
new Date(new Date().getTime() + time_sleep)));
|
2017-06-06 13:38:48 +02:00
|
|
|
try {
|
2020-07-28 00:49:36 +10:00
|
|
|
int timeSlept = 0;
|
|
|
|
|
while (timeSlept < time_sleep && this.running && !this.shuttingdown) {
|
|
|
|
|
Thread.sleep(1000);
|
|
|
|
|
timeSlept += 1000;
|
|
|
|
|
}
|
2017-06-06 13:38:48 +02:00
|
|
|
}
|
|
|
|
|
catch (InterruptedException e1) {
|
2020-04-27 22:28:04 +10:00
|
|
|
return -3;
|
2017-06-06 13:38:48 +02:00
|
|
|
}
|
2020-06-16 22:03:17 +10:00
|
|
|
this.log.removeCheckPoint(step);
|
2020-04-27 22:28:04 +10:00
|
|
|
continue; // go back to ask job
|
2017-06-06 13:38:48 +02:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
catch (FermeExceptionServerInMaintenance e) {
|
|
|
|
|
int wait = ThreadLocalRandom.current().nextInt(20, 30 + 1); // max is exclusive
|
|
|
|
|
int time_sleep = 1000 * 60 * wait;
|
2020-05-16 18:42:16 +10:00
|
|
|
this.gui.status(String.format("The server is under maintenance and cannot allocate a job. Will try again at %tR",
|
|
|
|
|
new Date(new Date().getTime() + time_sleep)));
|
2014-11-20 13:21:19 +00:00
|
|
|
try {
|
2020-07-28 00:49:36 +10:00
|
|
|
int timeSlept = 0;
|
|
|
|
|
while (timeSlept < time_sleep && this.running && !this.shuttingdown) {
|
|
|
|
|
Thread.sleep(1000);
|
|
|
|
|
timeSlept += 1000;
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
catch (InterruptedException e1) {
|
|
|
|
|
return -3;
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2020-06-16 22:03:17 +10:00
|
|
|
this.log.removeCheckPoint(step);
|
2020-04-27 22:28:04 +10:00
|
|
|
continue; // go back to ask job
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
catch (FermeExceptionBadResponseFromServer e) {
|
|
|
|
|
int wait = ThreadLocalRandom.current().nextInt(15, 30 + 1); // max is exclusive
|
|
|
|
|
int time_sleep = 1000 * 60 * wait;
|
2020-05-28 13:28:42 +02:00
|
|
|
this.gui.status(String.format("Bad answer from the server. Will try again at %tR", new Date(new Date().getTime() + time_sleep)));
|
2020-04-27 22:28:04 +10:00
|
|
|
try {
|
2020-07-28 00:49:36 +10:00
|
|
|
int timeSlept = 0;
|
|
|
|
|
while (timeSlept < time_sleep && this.running && !this.shuttingdown) {
|
|
|
|
|
Thread.sleep(1000);
|
|
|
|
|
timeSlept += 1000;
|
|
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
}
|
|
|
|
|
catch (InterruptedException e1) {
|
|
|
|
|
return -3;
|
|
|
|
|
}
|
2020-06-16 22:03:17 +10:00
|
|
|
this.log.removeCheckPoint(step);
|
2020-04-27 22:28:04 +10:00
|
|
|
continue; // go back to ask job
|
2016-03-16 14:07:42 +01:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
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);
|
2020-06-16 22:03:17 +10:00
|
|
|
this.log.removeCheckPoint(step);
|
2020-04-27 22:28:04 +10:00
|
|
|
continue;
|
2016-03-16 14:07:42 +01:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
|
|
|
|
|
if (this.renderingJob == null) { // no job
|
2021-09-09 21:33:21 +02:00
|
|
|
int[] retrySchemeInMilliSeconds = { 300000, 480000, 720000, 900000, 1200000 }; // 5, 8, 12, 15 and 20 minutes
|
2020-05-20 22:32:39 +10:00
|
|
|
|
2021-09-09 21:33:21 +02:00
|
|
|
int time_sleep = retrySchemeInMilliSeconds[(this.noJobRetryIter < retrySchemeInMilliSeconds.length) ?
|
2020-05-28 13:28:42 +02:00
|
|
|
this.noJobRetryIter++ :
|
2021-09-09 21:33:21 +02:00
|
|
|
(retrySchemeInMilliSeconds.length - 1)];
|
2020-05-28 13:28:42 +02:00
|
|
|
this.gui.status(String.format("No job available. Will try again at %tR", new Date(new Date().getTime() + time_sleep)));
|
2020-04-27 22:28:04 +10:00
|
|
|
int time_slept = 0;
|
2021-10-02 19:56:54 +00:00
|
|
|
while (time_slept < time_sleep && this.running && this.shuttingdown == false) {
|
2020-04-27 22:28:04 +10:00
|
|
|
try {
|
|
|
|
|
Thread.sleep(250);
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e) {
|
|
|
|
|
return -3;
|
|
|
|
|
}
|
|
|
|
|
time_slept += 250;
|
|
|
|
|
}
|
2020-06-16 22:03:17 +10:00
|
|
|
this.log.removeCheckPoint(step);
|
2020-04-27 22:28:04 +10:00
|
|
|
continue; // go back to ask job
|
2016-03-16 14:07:42 +01:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
|
|
|
|
|
this.log.debug("Got work to do id: " + this.renderingJob.getId() + " frame: " + this.renderingJob.getFrameNumber());
|
|
|
|
|
|
2020-05-20 22:32:39 +10:00
|
|
|
// As the server allocated a new job to this client, reset the no_job waiting algorithm
|
|
|
|
|
this.noJobRetryIter = 0;
|
|
|
|
|
|
2020-04-27 22:28:04 +10:00
|
|
|
ret = this.work(this.renderingJob);
|
2022-03-11 14:02:22 +00:00
|
|
|
if (ret == Error.Type.NO_SPACE_LEFT_ON_DEVICE || ret == Error.Type.PATH_INVALID || ret == Error.Type.NO_WRITE_PERMISSION ) {
|
2020-04-27 22:28:04 +10:00
|
|
|
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;
|
2015-07-08 19:44:38 +01:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
|
|
|
|
|
if (ret != Error.Type.OK) {
|
2020-08-29 16:17:34 +02:00
|
|
|
Job currentJob = this.renderingJob; // copy it because the sendError will take ~5min to execute
|
2020-04-27 22:28:04 +10:00
|
|
|
this.renderingJob = null;
|
|
|
|
|
this.gui.error(Error.humanString(ret));
|
2020-08-29 16:17:34 +02:00
|
|
|
this.sendError(step, currentJob, ret);
|
2020-04-27 22:28:04 +10:00
|
|
|
this.log.removeCheckPoint(step);
|
2020-08-30 00:15:01 +10:00
|
|
|
|
|
|
|
|
// Initial test frames always have the Job ID below 20. If we have any error while trying to render the initial frame just
|
|
|
|
|
// halt the execution
|
2021-12-21 10:02:32 +00:00
|
|
|
if (Integer.parseInt(currentJob.getId()) < MIN_JOB_ID) {
|
2020-08-30 00:15:01 +10:00
|
|
|
// Add the proper explanation to the existing error message and keep the client waiting forever to ensure the user sees the error
|
|
|
|
|
this.gui.error(Error.humanString(ret) + " The error happened during the test frame render. Restart the client and try again.");
|
2021-10-02 19:56:54 +00:00
|
|
|
while (shuttingdown == false) {
|
2020-08-30 00:15:01 +10:00
|
|
|
try {
|
|
|
|
|
Thread.sleep(1000);
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e1) {
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-09-10 11:42:33 +02:00
|
|
|
break; // if the shutdown signal is triggered then exit the while (this.running) loop to initiate the shutdown process
|
2020-08-30 00:15:01 +10:00
|
|
|
}
|
|
|
|
|
|
2020-04-27 22:28:04 +10:00
|
|
|
continue;
|
2015-07-08 19:44:38 +01:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
|
2021-09-10 11:42:33 +02:00
|
|
|
if (this.renderingJob.isSynchronousUpload()) { // power or compute_method job, need to upload right away
|
2020-05-28 13:28:42 +02:00
|
|
|
this.gui.status(String.format("Uploading frame (%.2fMB)", (this.renderingJob.getOutputImageSize() / 1024.0 / 1024.0)));
|
2020-04-27 22:28:04 +10:00
|
|
|
|
2020-06-18 21:57:52 +10:00
|
|
|
ret = confirmJob(this.renderingJob, step);
|
2020-04-27 22:28:04 +10:00
|
|
|
if (ret != Error.Type.OK) {
|
|
|
|
|
gui.error("Client::run problem with confirmJob (returned " + ret + ")");
|
2020-05-16 10:51:22 +02:00
|
|
|
sendError(step, this.renderingJob, Error.Type.VALIDATION_FAILED);
|
2020-04-27 22:28:04 +10:00
|
|
|
}
|
2016-09-11 13:46:12 +02:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
else {
|
2020-05-28 13:28:42 +02:00
|
|
|
this.gui.status(String.format("Queuing frame for upload (%.2fMB)", (this.renderingJob.getOutputImageSize() / 1024.0 / 1024.0)));
|
2020-04-27 22:28:04 +10:00
|
|
|
|
2020-06-18 21:57:52 +10:00
|
|
|
this.jobsToValidate.add(new QueuedJob(step, this.renderingJob));
|
2020-04-27 22:28:04 +10:00
|
|
|
|
|
|
|
|
this.uploadQueueSize++;
|
|
|
|
|
this.uploadQueueVolume += this.renderingJob.getOutputImageSize();
|
|
|
|
|
this.gui.displayUploadQueueStats(uploadQueueSize, uploadQueueVolume);
|
|
|
|
|
|
|
|
|
|
this.renderingJob = null;
|
2016-09-11 13:46:12 +02:00
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
|
2021-09-10 11:42:33 +02:00
|
|
|
if (this.shouldWaitBeforeRender()) {
|
2020-05-11 21:51:36 +10:00
|
|
|
this.gui.status("Sending frames. Please wait");
|
|
|
|
|
|
2021-09-10 11:42:33 +02:00
|
|
|
while (this.shouldWaitBeforeRender()) {
|
2020-05-11 21:51:36 +10:00
|
|
|
try {
|
|
|
|
|
Thread.sleep(4000); // wait a little bit
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e3) {
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.log.removeCheckPoint(step);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-27 22:28:04 +10:00
|
|
|
// 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
|
2014-11-20 13:21:19 +00:00
|
|
|
try {
|
|
|
|
|
Thread.sleep(2300); // wait a little bit
|
2020-04-27 22:28:04 +10:00
|
|
|
this.gui.status("Uploading rendered frames before exiting. Please wait");
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
catch (InterruptedException e3) {
|
|
|
|
|
}
|
2020-04-27 22:28:04 +10:00
|
|
|
|
|
|
|
|
// This loop will remain valid until all the background uploads have
|
|
|
|
|
// finished (unless the stop() method has been triggered)
|
2020-05-28 13:28:42 +02:00
|
|
|
}
|
|
|
|
|
while (this.uploadQueueSize > 0);
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
catch (Exception e1) {
|
2014-12-16 23:02:14 +00:00
|
|
|
// no exception should be raised in the actual launcher (applet or standalone)
|
2015-04-30 20:51:04 +01:00
|
|
|
StringWriter sw = new StringWriter();
|
|
|
|
|
PrintWriter pw = new PrintWriter(sw);
|
|
|
|
|
e1.printStackTrace(pw);
|
|
|
|
|
this.log.debug("Client::run exception(D) " + e1 + " stacktrace: " + sw.toString());
|
2014-11-20 13:21:19 +00:00
|
|
|
return -99; // the this.stop will be done after the return of this.run()
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-28 00:49:36 +10:00
|
|
|
if (this.shuttingdown) {
|
|
|
|
|
// Shutdown the computer using the appropriate command for the host OS
|
|
|
|
|
this.log.debug("Shutting down the computer in 1 minute");
|
|
|
|
|
|
|
|
|
|
OS.getOS().shutdownComputer(1);
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-05 19:58:11 +01:00
|
|
|
this.gui.stop();
|
2014-11-20 13:21:19 +00:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public synchronized int stop() {
|
|
|
|
|
this.running = false;
|
|
|
|
|
this.disableErrorSending = true;
|
|
|
|
|
|
|
|
|
|
if (this.renderingJob != null) {
|
2020-04-11 19:53:55 +10:00
|
|
|
this.gui.status("Stopping");
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2015-04-27 20:10:36 +01:00
|
|
|
if (this.renderingJob.getProcessRender().getProcess() != null) {
|
2014-12-03 20:09:50 +00:00
|
|
|
this.renderingJob.setAskForRendererKill(true);
|
2018-08-29 15:14:11 +02:00
|
|
|
OS.getOS().kill(this.renderingJob.getProcessRender().getProcess());
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
2022-03-13 12:26:11 +00:00
|
|
|
|
2019-08-07 22:17:59 +02:00
|
|
|
this.configuration.removeWorkingDirectory();
|
2014-11-20 13:21:19 +00:00
|
|
|
|
|
|
|
|
if (this.server == null) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2020-01-12 14:37:30 +01:00
|
|
|
if (this.server.getPage("logout").isEmpty() == false) {
|
2020-04-11 19:53:55 +10:00
|
|
|
this.gui.status("Disconnecting from SheepIt server");
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2020-01-12 14:37:30 +01:00
|
|
|
try {
|
|
|
|
|
this.server.HTTPRequest(this.server.getPage("logout"));
|
|
|
|
|
}
|
|
|
|
|
catch (IOException e) {
|
|
|
|
|
// nothing to do: if the logout failed that's ok
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
this.server.interrupt();
|
|
|
|
|
try {
|
|
|
|
|
this.server.join();
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.server = null;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-02 20:28:03 +00:00
|
|
|
public void suspend() {
|
|
|
|
|
suspended = true;
|
2020-04-26 23:35:05 +10:00
|
|
|
this.gui.status("Client will pause when the current job finishes", true);
|
2014-12-02 20:28:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public synchronized void resume() {
|
|
|
|
|
suspended = false;
|
|
|
|
|
notify();
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
public void askForStop() {
|
2015-08-05 21:15:39 +01:00
|
|
|
this.log.debug("Client::askForStop");
|
2014-11-20 13:21:19 +00:00
|
|
|
this.running = false;
|
|
|
|
|
}
|
2017-01-05 09:35:59 +01:00
|
|
|
|
2015-08-05 19:58:11 +01:00
|
|
|
public void cancelStop() {
|
2015-08-05 21:15:39 +01:00
|
|
|
this.log.debug("Client::cancelStop");
|
2015-08-05 19:58:11 +01:00
|
|
|
this.running = true;
|
|
|
|
|
}
|
2017-01-05 09:35:59 +01:00
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
public int senderLoop() {
|
2020-06-18 21:57:52 +10:00
|
|
|
int step = -1;
|
|
|
|
|
Error.Type ret = null;
|
2014-11-20 13:21:19 +00:00
|
|
|
while (true) {
|
2020-06-18 21:57:52 +10:00
|
|
|
QueuedJob queuedJob = null;
|
2014-11-20 13:21:19 +00:00
|
|
|
try {
|
2020-06-18 21:57:52 +10:00
|
|
|
queuedJob = jobsToValidate.take();
|
|
|
|
|
step = queuedJob.checkpoint; // retrieve the checkpoint attached to the job
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2020-06-18 21:57:52 +10:00
|
|
|
this.log.debug(step, "will validate " + queuedJob.job);
|
|
|
|
|
|
|
|
|
|
ret = confirmJob(queuedJob.job, step);
|
2014-11-20 13:21:19 +00:00
|
|
|
if (ret != Error.Type.OK) {
|
2014-12-16 23:02:14 +00:00
|
|
|
this.gui.error(Error.humanString(ret));
|
2020-06-18 21:57:52 +10:00
|
|
|
this.log.debug(step, "Client::senderLoop confirm failed, ret: " + ret);
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e) {
|
2020-06-18 21:57:52 +10:00
|
|
|
this.log.error(step, "Client::senderLoop Exception " + e.getMessage());
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2020-05-10 06:04:07 +10:00
|
|
|
finally {
|
2020-06-18 21:57:52 +10:00
|
|
|
if (ret != Error.Type.OK) {
|
|
|
|
|
if (queuedJob.job != null) {
|
|
|
|
|
sendError(step, queuedJob.job, ret);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
sendError(step);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove the checkpoint information
|
|
|
|
|
log.removeCheckPoint(step);
|
|
|
|
|
|
2020-05-10 06:04:07 +10:00
|
|
|
this.uploadQueueSize--;
|
2020-06-18 21:57:52 +10:00
|
|
|
if (queuedJob.job != null) {
|
|
|
|
|
this.uploadQueueVolume -= queuedJob.job.getOutputImageSize();
|
2020-05-10 06:04:07 +10:00
|
|
|
}
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2020-05-10 06:04:07 +10:00
|
|
|
this.gui.displayUploadQueueStats(this.uploadQueueSize, this.uploadQueueVolume);
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void sendError(int step_) {
|
|
|
|
|
this.sendError(step_, null, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void sendError(int step_, Job job_to_reset_, Error.Type error) {
|
|
|
|
|
if (this.disableErrorSending) {
|
2014-12-23 20:05:29 +01:00
|
|
|
this.log.debug("Error sending is disabled, do not send log");
|
2014-11-20 13:21:19 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-29 21:21:17 +01:00
|
|
|
this.log.debug("Sending error to server (type: " + error + ")");
|
2014-11-20 13:21:19 +00:00
|
|
|
try {
|
2020-06-21 19:16:18 +10:00
|
|
|
File temp_file = File.createTempFile("farm_", ".txt");
|
2014-11-20 13:21:19 +00:00
|
|
|
temp_file.createNewFile();
|
|
|
|
|
temp_file.deleteOnExit();
|
|
|
|
|
FileOutputStream writer = new FileOutputStream(temp_file);
|
|
|
|
|
|
2020-06-03 00:06:03 +10:00
|
|
|
// Create a header with the information summarised for easier admin error analysis
|
|
|
|
|
Configuration conf = this.configuration;
|
|
|
|
|
CPU cpu = OS.getOS().getCPU();
|
|
|
|
|
|
|
|
|
|
StringBuilder logHeader = new StringBuilder()
|
|
|
|
|
.append("====================================================================================================\n")
|
2021-12-30 17:46:20 +00:00
|
|
|
.append(String.format("%s / %s / %s / SheepIt v%s\n", conf.getLogin(), conf.getHostname(), OS.getOS().name(), Configuration.jarVersion))
|
2021-11-16 14:51:53 +00:00
|
|
|
.append(String.format("%s x%d %.1f GB RAM\n", cpu.name(), conf.getNbCores(), conf.getMaxAllowedMemory() / 1024.0 / 1024.0));
|
2020-06-03 00:06:03 +10:00
|
|
|
|
|
|
|
|
if (conf.getComputeMethod() == Configuration.ComputeType.GPU || conf.getComputeMethod() == Configuration.ComputeType.CPU_GPU) {
|
2020-10-30 07:41:38 +11:00
|
|
|
logHeader.append(String.format("%s %s %.1f GB VRAM\n", conf.getGPUDevice().getId(), conf.getGPUDevice().getModel(),
|
|
|
|
|
conf.getGPUDevice().getMemory() / 1024.0 / 1024.0 / 1024.0));
|
2020-06-03 00:06:03 +10:00
|
|
|
}
|
|
|
|
|
|
2020-06-04 19:50:00 +10:00
|
|
|
logHeader.append("====================================================================================================\n");
|
|
|
|
|
if (job_to_reset_ != null) {
|
2020-10-30 07:41:38 +11:00
|
|
|
logHeader.append(String.format("Project ::: %s\n", job_to_reset_.getName()))
|
|
|
|
|
.append(String.format("Project id: %s frame: %s\n", job_to_reset_.getId(), job_to_reset_.getFrameNumber())).append(String.format("blender ::: %s\n\n", job_to_reset_.getBlenderLongVersion())).append(String.format("ERROR Type :: %s\n", error));
|
2020-06-04 19:50:00 +10:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
logHeader.append("Project ::: No project allocated.\n")
|
|
|
|
|
.append(String.format("ERROR Type :: %s\n", (error != null ? error : "N/A")));
|
|
|
|
|
}
|
|
|
|
|
logHeader.append("====================================================================================================\n\n");
|
2020-06-03 00:06:03 +10:00
|
|
|
|
2020-06-04 19:50:00 +10:00
|
|
|
// Insert the info at the beginning of the error log
|
2020-06-03 00:06:03 +10:00
|
|
|
writer.write(logHeader.toString().getBytes());
|
|
|
|
|
|
2020-10-27 14:32:11 +01:00
|
|
|
Optional<ArrayList<String>> logs = this.log.getForCheckPoint(step_);
|
|
|
|
|
if (logs.isPresent()) {
|
|
|
|
|
for (String line : logs.get()) {
|
|
|
|
|
writer.write(line.getBytes());
|
|
|
|
|
writer.write('\n');
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
writer.close();
|
2021-09-09 21:29:00 +02:00
|
|
|
HttpUrl.Builder remoteURL = HttpUrl.parse(this.server.getPage("error")).newBuilder();
|
|
|
|
|
remoteURL.addQueryParameter("type", error == null ? "" : Integer.toString(error.getValue()));
|
2014-11-20 13:21:19 +00:00
|
|
|
if (job_to_reset_ != null) {
|
2021-09-09 21:29:00 +02:00
|
|
|
remoteURL.addQueryParameter("frame", job_to_reset_.getFrameNumber());
|
|
|
|
|
remoteURL.addQueryParameter("job", job_to_reset_.getId());
|
|
|
|
|
remoteURL.addQueryParameter("render_time", Integer.toString(job_to_reset_.getProcessRender().getDuration()));
|
2021-11-16 14:51:53 +00:00
|
|
|
remoteURL.addQueryParameter("memoryused", Long.toString(job_to_reset_.getProcessRender().getPeakMemoryUsed()));
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2021-09-09 21:29:00 +02:00
|
|
|
this.server.HTTPSendFile(remoteURL.build().toString(), temp_file.getAbsolutePath(), step_, this.gui);
|
2014-11-20 13:21:19 +00:00
|
|
|
temp_file.delete();
|
|
|
|
|
}
|
2017-04-19 21:23:12 +02:00
|
|
|
catch (Exception e) {
|
|
|
|
|
StringWriter sw = new StringWriter();
|
|
|
|
|
PrintWriter pw = new PrintWriter(sw);
|
|
|
|
|
e.printStackTrace(pw);
|
|
|
|
|
this.log.debug("Client::sendError Exception " + e + " stacktrace: " + sw.toString());
|
2014-12-23 20:05:29 +01:00
|
|
|
// no exception should be raised to actual launcher (applet or standalone)
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
|
2020-05-28 13:28:42 +02:00
|
|
|
if (error != null && (error == Error.Type.RENDERER_CRASHED || error == Error.Type.RENDERER_KILLED_BY_USER
|
2020-06-08 18:07:49 +10:00
|
|
|
|| error == Type.RENDERER_KILLED_BY_USER_OVER_TIME || error == Error.Type.RENDERER_KILLED_BY_SERVER)) {
|
2014-11-20 13:21:19 +00:00
|
|
|
// do nothing, we can ask for a job right away
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
try {
|
|
|
|
|
Thread.sleep(300000); // sleeping for 5min
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2014-12-23 20:05:29 +01:00
|
|
|
* @return the date of the next request, or null if there is not delay (null <=> now)
|
2014-11-20 13:21:19 +00:00
|
|
|
*/
|
|
|
|
|
public Calendar nextJobRequest() {
|
2019-08-07 22:17:59 +02:00
|
|
|
if (this.configuration.getRequestTime() == null) {
|
2014-11-20 13:21:19 +00:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
Calendar next = null;
|
|
|
|
|
Calendar now = Calendar.getInstance();
|
2019-08-07 22:17:59 +02:00
|
|
|
for (Pair<Calendar, Calendar> interval : this.configuration.getRequestTime()) {
|
2014-11-20 13:21:19 +00:00
|
|
|
Calendar start = (Calendar) now.clone();
|
|
|
|
|
Calendar end = (Calendar) now.clone();
|
|
|
|
|
start.set(Calendar.SECOND, 00);
|
|
|
|
|
start.set(Calendar.MINUTE, interval.first.get(Calendar.MINUTE));
|
|
|
|
|
start.set(Calendar.HOUR_OF_DAY, interval.first.get(Calendar.HOUR_OF_DAY));
|
|
|
|
|
|
|
|
|
|
end.set(Calendar.SECOND, 59);
|
|
|
|
|
end.set(Calendar.MINUTE, interval.second.get(Calendar.MINUTE));
|
|
|
|
|
end.set(Calendar.HOUR_OF_DAY, interval.second.get(Calendar.HOUR_OF_DAY));
|
|
|
|
|
|
|
|
|
|
if (start.before(now) && now.before(end)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2019-03-06 21:12:08 +01:00
|
|
|
if (next == null || (start.before(next) && start.after(now))) {
|
|
|
|
|
next = start;
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-07 18:40:02 +02:00
|
|
|
public Error.Type work(final Job ajob) {
|
2020-06-21 02:36:55 +10:00
|
|
|
Error.Type downloadRet;
|
2014-11-20 13:21:19 +00:00
|
|
|
|
2016-10-12 00:34:51 +02:00
|
|
|
gui.setRenderingProjectName(ajob.getName());
|
|
|
|
|
|
2016-05-02 13:26:11 +02:00
|
|
|
try {
|
2020-06-21 02:36:55 +10:00
|
|
|
downloadRet = this.downloadExecutable(ajob);
|
|
|
|
|
if (downloadRet != Error.Type.OK) {
|
2016-10-12 00:34:51 +02:00
|
|
|
gui.setRenderingProjectName("");
|
2020-06-21 02:36:55 +10:00
|
|
|
this.log.error("Client::work problem with downloadExecutable (ret " + downloadRet + ")");
|
|
|
|
|
return downloadRet;
|
2016-05-02 13:26:11 +02:00
|
|
|
}
|
|
|
|
|
|
2020-06-21 02:36:55 +10:00
|
|
|
downloadRet = this.downloadSceneFile(ajob);
|
|
|
|
|
if (downloadRet != Error.Type.OK) {
|
2016-10-12 00:34:51 +02:00
|
|
|
gui.setRenderingProjectName("");
|
2020-06-21 02:36:55 +10:00
|
|
|
this.log.error("Client::work problem with downloadSceneFile (ret " + downloadRet + ")");
|
|
|
|
|
return downloadRet;
|
2016-05-02 13:26:11 +02:00
|
|
|
}
|
|
|
|
|
|
2020-06-21 02:36:55 +10:00
|
|
|
int ret = this.prepareWorkingDirectory(ajob); // decompress renderer and scene archives
|
2016-05-02 13:26:11 +02:00
|
|
|
if (ret != 0) {
|
2016-10-12 00:34:51 +02:00
|
|
|
gui.setRenderingProjectName("");
|
2016-05-02 13:26:11 +02:00
|
|
|
this.log.error("Client::work problem with this.prepareWorkingDirectory (ret " + ret + ")");
|
|
|
|
|
return Error.Type.CAN_NOT_CREATE_DIRECTORY;
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2022-03-11 14:02:22 +00:00
|
|
|
catch (FermeException e) {
|
2016-10-12 00:34:51 +02:00
|
|
|
gui.setRenderingProjectName("");
|
2022-03-11 14:02:22 +00:00
|
|
|
if (e instanceof FermeExceptionNoSpaceLeftOnDevice) {
|
|
|
|
|
return Error.Type.NO_SPACE_LEFT_ON_DEVICE;
|
|
|
|
|
}
|
|
|
|
|
else if (e instanceof FermeExceptionPathInvalid) {
|
|
|
|
|
return Error.Type.PATH_INVALID;
|
|
|
|
|
}
|
|
|
|
|
else if (e instanceof FermeExceptionNoWritePermission) {
|
|
|
|
|
return Error.Type.NO_WRITE_PERMISSION;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return Error.Type.UNKNOWN;
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
|
2019-08-22 21:34:34 +02:00
|
|
|
final File scene_file = new File(ajob.getScenePath());
|
2014-11-20 13:21:19 +00:00
|
|
|
File renderer_file = new File(ajob.getRendererPath());
|
|
|
|
|
|
|
|
|
|
if (scene_file.exists() == false) {
|
2016-10-12 00:34:51 +02:00
|
|
|
gui.setRenderingProjectName("");
|
2020-05-28 13:28:42 +02:00
|
|
|
this.log.error("Client::work job preparation failed (scene file '" + scene_file.getAbsolutePath()
|
|
|
|
|
+ "' does not exist), cleaning directory in hope to recover");
|
2019-08-07 22:17:59 +02:00
|
|
|
this.configuration.cleanWorkingDirectory();
|
2014-11-20 13:21:19 +00:00
|
|
|
return Error.Type.MISSING_SCENE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (renderer_file.exists() == false) {
|
2016-10-12 00:34:51 +02:00
|
|
|
gui.setRenderingProjectName("");
|
2020-05-28 13:28:42 +02:00
|
|
|
this.log.error("Client::work job preparation failed (renderer file '" + renderer_file.getAbsolutePath()
|
|
|
|
|
+ "' does not exist), cleaning directory in hope to recover");
|
2019-08-07 22:17:59 +02:00
|
|
|
this.configuration.cleanWorkingDirectory();
|
2019-02-09 05:41:46 +01:00
|
|
|
return Error.Type.MISSING_RENDERER;
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2019-08-07 18:40:02 +02:00
|
|
|
Observer removeSceneDirectoryOnceRenderHasStartedObserver = new Observer() {
|
2020-05-28 13:28:42 +02:00
|
|
|
@Override public void update(Observable observable, Object o) {
|
2019-08-22 21:34:34 +02:00
|
|
|
// 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();
|
2019-08-07 18:40:02 +02:00
|
|
|
}
|
|
|
|
|
};
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2019-08-07 18:40:02 +02:00
|
|
|
Error.Type err = ajob.render(removeSceneDirectoryOnceRenderHasStartedObserver);
|
2016-10-12 00:34:51 +02:00
|
|
|
gui.setRenderingProjectName("");
|
|
|
|
|
gui.setRemainingTime("");
|
|
|
|
|
gui.setRenderingTime("");
|
2017-05-07 21:00:20 +02:00
|
|
|
gui.setComputeMethod("");
|
2014-11-20 13:21:19 +00:00
|
|
|
if (err != Error.Type.OK) {
|
|
|
|
|
this.log.error("Client::work problem with runRenderer (ret " + err + ")");
|
2019-02-22 13:53:47 +01:00
|
|
|
if (err == Error.Type.RENDERER_CRASHED_PYTHON_ERROR) {
|
|
|
|
|
this.log.error("Client::work failed with python error, cleaning directory in hope to recover");
|
2019-08-07 22:17:59 +02:00
|
|
|
this.configuration.cleanWorkingDirectory();
|
2019-02-22 13:53:47 +01:00
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
return err;
|
|
|
|
|
}
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2019-08-07 17:02:52 +02:00
|
|
|
removeSceneDirectory(ajob);
|
2014-11-20 13:21:19 +00:00
|
|
|
|
|
|
|
|
return Error.Type.OK;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-11 14:02:22 +00:00
|
|
|
protected Error.Type downloadSceneFile(Job ajob_) throws FermeException {
|
2020-10-21 22:03:09 +11:00
|
|
|
return this.downloadFile(ajob_, ajob_.getRequiredSceneArchivePath(), ajob_.getSceneMD5(),
|
2021-12-21 13:50:28 +00:00
|
|
|
String.format(LOCALE, "%s?type=job&job=%s", this.server.getPage("download-archive"), ajob_.getId()), "project");
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
|
2022-03-11 14:02:22 +00:00
|
|
|
protected Error.Type downloadExecutable(Job ajob) throws FermeException {
|
2020-10-21 22:03:09 +11:00
|
|
|
return this.downloadFile(ajob, ajob.getRequiredRendererArchivePath(), ajob.getRendererMD5(),
|
2021-12-21 13:50:28 +00:00
|
|
|
String.format(LOCALE, "%s?type=binary&job=%s", this.server.getPage("download-archive"), ajob.getId()), "renderer");
|
2015-04-28 18:46:04 +01:00
|
|
|
}
|
|
|
|
|
|
2022-03-11 14:02:22 +00:00
|
|
|
private Error.Type downloadFile(Job ajob, String local_path, String md5_server, String url, String download_type) throws FermeException {
|
2015-04-28 18:46:04 +01:00
|
|
|
File local_path_file = new File(local_path);
|
2020-06-14 20:01:32 +10:00
|
|
|
String update_ui = "Downloading " + download_type;
|
2014-11-20 13:21:19 +00:00
|
|
|
|
2020-10-21 22:03:09 +11:00
|
|
|
int remaining = 1800000; // 30 minutes max timeout
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// If the client is using a shared cache then introduce some random delay to minimise race conditions on the partial file creation on multiple
|
|
|
|
|
// instances of a client (when started with a script or rendering a recently downloaded scene)
|
|
|
|
|
if (configuration.getSharedDownloadsDirectory() != null) {
|
|
|
|
|
Thread.sleep((new Random().nextInt(9) + 1) * 1000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For a maximum of 30 minutes
|
|
|
|
|
do {
|
|
|
|
|
// if the binary or scene already exists in the cache
|
2021-09-10 11:42:33 +02:00
|
|
|
if (local_path_file.exists()) {
|
2020-10-21 22:03:09 +11:00
|
|
|
this.gui.status("Reusing cached " + download_type);
|
|
|
|
|
return Type.OK;
|
|
|
|
|
}
|
|
|
|
|
// if the binary or scene is being downloaded by another client
|
|
|
|
|
else if (new File(local_path + ".partial").exists()) {
|
|
|
|
|
// Wait and check every second for file download completion but only update the GUI every 10 seconds to minimise CPU load
|
|
|
|
|
if (remaining % 10000 == 0) {
|
|
|
|
|
this.gui.status(String.format("Another client is downloading the %s. Cancel in %dmin %ds",
|
|
|
|
|
download_type,
|
|
|
|
|
TimeUnit.MILLISECONDS.toMinutes(remaining),
|
|
|
|
|
TimeUnit.MILLISECONDS.toSeconds(remaining) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(remaining))
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// The file doesn't yet existing not is being downloaded by another client, so immediately create the file with zero bytes to allow early
|
|
|
|
|
// detection by other concurrent clients and start downloading process
|
|
|
|
|
try {
|
|
|
|
|
File file = new File(local_path + ".partial");
|
|
|
|
|
file.createNewFile();
|
|
|
|
|
file.deleteOnExit(); // if the client crashes, the temporary file will be removed
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
StringWriter sw = new StringWriter();
|
|
|
|
|
e.printStackTrace(new PrintWriter(sw));
|
|
|
|
|
this.log.error("Client::DownloadFile Unable to create .partial temp file for binary/scene " + local_path);
|
|
|
|
|
this.log.error("Client::DownloadFile Exception " + e + " stacktrace " + sw.toString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reduce 1 second the waiting time
|
|
|
|
|
Thread.sleep(1000);
|
|
|
|
|
remaining -= 1000;
|
|
|
|
|
} while (remaining > 0);
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e) {
|
|
|
|
|
log.debug("Error in the thread wait. Exception " + e.getMessage());
|
|
|
|
|
}
|
|
|
|
|
finally {
|
|
|
|
|
// If we have reached the timeout (30 minutes trying to download the client) delete the partial downloaded copy and try to download again
|
|
|
|
|
if (remaining <= 0) {
|
|
|
|
|
log.debug("ERROR while waiting for download to finish in another client. Deleting the partial file and downloading a fresh copy now!.");
|
|
|
|
|
new File(local_path + ".partial").delete();
|
|
|
|
|
}
|
2015-05-05 18:35:25 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-29 16:40:21 +02:00
|
|
|
this.gui.status(String.format("Downloading %s", download_type));
|
2016-01-13 09:47:41 +01:00
|
|
|
|
2015-05-05 18:35:25 +02:00
|
|
|
// must download the archive
|
2020-06-21 02:36:55 +10:00
|
|
|
Error.Type ret = this.server.HTTPGetFile(url, local_path, this.gui, update_ui);
|
|
|
|
|
|
2020-06-21 19:15:53 +10:00
|
|
|
if (ret == Type.RENDERER_KILLED_BY_SERVER || ret == Type.RENDERER_KILLED_BY_USER_OVER_TIME || ret == Type.RENDERER_KILLED_BY_USER) {
|
2020-06-21 02:36:55 +10:00
|
|
|
return ret;
|
|
|
|
|
}
|
2020-06-01 19:15:44 +10:00
|
|
|
|
|
|
|
|
// Try to check the download file even if a download error has occurred (MD5 file check will delete the file if partially downloaded)
|
2015-05-05 18:35:25 +02:00
|
|
|
boolean md5_check = this.checkFile(ajob, local_path, md5_server);
|
|
|
|
|
int attempts = 1;
|
|
|
|
|
|
2020-06-21 02:36:55 +10:00
|
|
|
while ((ret != Error.Type.OK || md5_check == false) && attempts < this.maxDownloadFileAttempts) {
|
|
|
|
|
if (ret != Error.Type.OK) {
|
2020-06-21 19:15:30 +10:00
|
|
|
this.gui.error(String.format("Unable to download %s (error %s). Retrying now", download_type, ret));
|
2016-07-29 23:25:05 +02:00
|
|
|
this.log.debug("Client::downloadFile problem with Server.HTTPGetFile (return: " + ret + ") removing local file (path: " + local_path + ")");
|
2015-05-05 18:35:25 +02:00
|
|
|
}
|
|
|
|
|
else if (md5_check == false) {
|
2020-04-11 19:53:55 +10:00
|
|
|
this.gui.error(String.format("Verification of downloaded %s has failed. Retrying now", download_type));
|
2015-05-05 18:35:25 +02:00
|
|
|
this.log.debug("Client::downloadFile problem with Client::checkFile mismatch on md5, removing local file (path: " + local_path + ")");
|
|
|
|
|
}
|
|
|
|
|
local_path_file.delete();
|
|
|
|
|
|
|
|
|
|
this.log.debug("Client::downloadFile failed, let's try again (" + (attempts + 1) + "/" + this.maxDownloadFileAttempts + ") ...");
|
|
|
|
|
|
|
|
|
|
ret = this.server.HTTPGetFile(url, local_path, this.gui, update_ui);
|
2020-06-21 02:36:55 +10:00
|
|
|
|
2015-05-05 18:35:25 +02:00
|
|
|
md5_check = this.checkFile(ajob, local_path, md5_server);
|
|
|
|
|
attempts++;
|
|
|
|
|
|
2020-06-21 02:36:55 +10:00
|
|
|
if ((ret != Error.Type.OK || md5_check == false) && attempts >= this.maxDownloadFileAttempts) {
|
2020-05-28 13:28:42 +02:00
|
|
|
this.log.debug("Client::downloadFile failed after " + this.maxDownloadFileAttempts + " attempts, removing local file (path: " + local_path
|
|
|
|
|
+ "), stopping...");
|
2015-04-28 18:47:02 +01:00
|
|
|
local_path_file.delete();
|
2020-06-21 02:36:55 +10:00
|
|
|
return Type.DOWNLOAD_FILE;
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-21 02:36:55 +10:00
|
|
|
return Type.OK;
|
2015-05-05 18:35:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean checkFile(Job ajob, String local_path, String md5_server) {
|
|
|
|
|
File local_path_file = new File(local_path);
|
|
|
|
|
|
|
|
|
|
if (local_path_file.exists() == false) {
|
|
|
|
|
this.log.error("Client::checkFile cannot check md5 on a nonexistent file (path: " + local_path + ")");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-28 18:46:04 +01:00
|
|
|
String md5_local = Utils.md5(local_path);
|
2014-11-20 13:21:19 +00:00
|
|
|
|
2015-04-28 18:46:04 +01:00
|
|
|
if (md5_local.equals(md5_server) == false) {
|
2020-05-28 13:28:42 +02:00
|
|
|
this.log.error(
|
|
|
|
|
"Client::checkFile mismatch on md5 local: '" + md5_local + "' server: '" + md5_server + "' (local size: " + new File(local_path).length()
|
|
|
|
|
+ ")");
|
2015-05-05 18:35:25 +02:00
|
|
|
return false;
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2015-05-05 18:35:25 +02:00
|
|
|
|
|
|
|
|
return true;
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2019-08-07 17:02:52 +02:00
|
|
|
protected void removeSceneDirectory(Job ajob) {
|
|
|
|
|
Utils.delete(new File(ajob.getSceneDirectory()));
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
|
2022-03-11 14:02:22 +00:00
|
|
|
protected int prepareWorkingDirectory(Job ajob) {
|
2014-11-20 13:21:19 +00:00
|
|
|
int ret;
|
2020-10-21 22:03:09 +11:00
|
|
|
String bestRendererArchive = ajob.getRequiredRendererArchivePath();
|
2014-11-20 13:21:19 +00:00
|
|
|
String renderer_archive = ajob.getRendererArchivePath();
|
|
|
|
|
String renderer_path = ajob.getRendererDirectory();
|
|
|
|
|
File renderer_path_file = new File(renderer_path);
|
|
|
|
|
|
2020-10-21 22:03:09 +11:00
|
|
|
if (!new File(renderer_archive).exists()) {
|
|
|
|
|
this.gui.status("Copying renderer from shared downloads directory");
|
|
|
|
|
|
2022-02-18 02:20:57 +01:00
|
|
|
copySharedArchive(bestRendererArchive, renderer_archive);
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2020-10-21 22:03:09 +11:00
|
|
|
|
|
|
|
|
if (!renderer_path_file.exists()) {
|
2014-11-20 13:21:19 +00:00
|
|
|
// we create the directory
|
|
|
|
|
renderer_path_file.mkdir();
|
|
|
|
|
|
2020-10-21 22:03:09 +11:00
|
|
|
this.gui.status("Extracting renderer");
|
|
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
// unzip the archive
|
2018-06-30 01:41:54 +02:00
|
|
|
ret = Utils.unzipFileIntoDirectory(renderer_archive, renderer_path, null, log);
|
2014-11-20 13:21:19 +00:00
|
|
|
if (ret != 0) {
|
2020-05-28 13:28:42 +02:00
|
|
|
this.log.error(
|
|
|
|
|
"Client::prepareWorkingDirectory, error(1) with Utils.unzipFileIntoDirectory(" + renderer_archive + ", " + renderer_path + ") returned "
|
|
|
|
|
+ ret);
|
2020-04-11 19:53:55 +10:00
|
|
|
this.gui.error(String.format("Unable to extract the renderer (error %d)", ret));
|
2014-11-20 13:21:19 +00:00
|
|
|
return -1;
|
|
|
|
|
}
|
2016-10-05 23:01:03 +02:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
File f = new File(ajob.getRendererPath());
|
|
|
|
|
f.setExecutable(true);
|
|
|
|
|
}
|
2017-01-05 09:35:59 +01:00
|
|
|
catch (SecurityException e) {
|
2016-10-05 23:01:03 +02:00
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
|
2020-10-21 22:03:09 +11:00
|
|
|
String bestSceneArchive = ajob.getRequiredSceneArchivePath();
|
2014-11-20 13:21:19 +00:00
|
|
|
String scene_archive = ajob.getSceneArchivePath();
|
|
|
|
|
String scene_path = ajob.getSceneDirectory();
|
|
|
|
|
File scene_path_file = new File(scene_path);
|
|
|
|
|
|
2020-10-21 22:03:09 +11:00
|
|
|
if (!new File(scene_archive).exists()) {
|
|
|
|
|
this.gui.status("Copying scene from common directory");
|
2022-02-18 02:20:57 +01:00
|
|
|
copySharedArchive(bestSceneArchive, scene_archive);
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2020-10-21 22:03:09 +11:00
|
|
|
|
|
|
|
|
if (!scene_path_file.exists()) {
|
2014-11-20 13:21:19 +00:00
|
|
|
// we create the directory
|
|
|
|
|
scene_path_file.mkdir();
|
|
|
|
|
|
2020-10-21 22:03:09 +11:00
|
|
|
this.gui.status("Extracting project");
|
|
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
// unzip the archive
|
2019-08-07 22:17:59 +02:00
|
|
|
ret = Utils.unzipFileIntoDirectory(scene_archive, scene_path, ajob.getPassword(), log);
|
2014-11-20 13:21:19 +00:00
|
|
|
if (ret != 0) {
|
2020-05-28 13:28:42 +02:00
|
|
|
this.log.error(
|
|
|
|
|
"Client::prepareWorkingDirectory, error(2) with Utils.unzipFileIntoDirectory(" + scene_archive + ", " + scene_path + ") returned "
|
|
|
|
|
+ ret);
|
2020-04-11 19:53:55 +10:00
|
|
|
this.gui.error(String.format("Unable to extract the scene (error %d)", ret));
|
2014-11-20 13:21:19 +00:00
|
|
|
return -2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2022-02-27 20:46:58 +01:00
|
|
|
|
2022-02-18 02:20:57 +01:00
|
|
|
private void copySharedArchive(String existingArchive, String targetArchive) {
|
|
|
|
|
Path existingArchivePath = Paths.get(existingArchive);
|
|
|
|
|
Path targetArchivePath = Paths.get(targetArchive);
|
|
|
|
|
try {
|
|
|
|
|
try {
|
|
|
|
|
Files.createLink(targetArchivePath, existingArchivePath);
|
|
|
|
|
log.debug("Created hardlink from " + targetArchivePath + " to " + existingArchivePath);
|
|
|
|
|
}
|
2022-02-27 20:46:58 +01:00
|
|
|
catch (UnsupportedOperationException // underlying file system does not support hard-linking
|
|
|
|
|
| FileSystemException // cache-dir and shared-zip are on separate file systems, even though hard-linking is supported
|
|
|
|
|
| SecurityException // user is not allowed to create hard-links
|
|
|
|
|
ignore) {
|
2022-02-18 02:20:57 +01:00
|
|
|
// Creating hardlinks might not be supported on some filesystems
|
|
|
|
|
log.debug("Failed to create hardlink, falling back to copying file to " + targetArchivePath);
|
|
|
|
|
Files.copy(existingArchivePath, targetArchivePath, StandardCopyOption.REPLACE_EXISTING);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (IOException e) {
|
|
|
|
|
this.gui.error("Error while copying " + existingArchive + " from shared downloads directory to working dir");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-18 21:57:52 +10:00
|
|
|
protected Error.Type confirmJob(Job ajob, int checkpoint) {
|
2021-12-21 13:50:28 +00:00
|
|
|
String url_real = String.format(LOCALE, "%s&rendertime=%d&memoryused=%s", ajob.getValidationUrl(), ajob.getProcessRender().getDuration(),
|
2021-11-16 14:51:53 +00:00
|
|
|
ajob.getProcessRender().getPeakMemoryUsed());
|
2020-06-18 21:57:52 +10:00
|
|
|
this.log.debug(checkpoint, "Client::confirmeJob url " + url_real);
|
|
|
|
|
this.log.debug(checkpoint, "path frame " + ajob.getOutputImagePath());
|
2014-11-20 13:21:19 +00:00
|
|
|
|
|
|
|
|
this.isValidatingJob = true;
|
|
|
|
|
int max_try = 3;
|
2022-04-20 22:39:52 +02:00
|
|
|
int timeToSleep = 22000;
|
2014-11-20 13:21:19 +00:00
|
|
|
ServerCode ret = ServerCode.UNKNOWN;
|
2020-05-10 06:04:07 +10:00
|
|
|
Type confirmJobReturnCode = Error.Type.OK;
|
2020-05-28 13:28:42 +02:00
|
|
|
retryLoop:
|
2022-03-12 12:43:10 +01:00
|
|
|
for (int nb_try = 0; nb_try < max_try; nb_try++) {
|
|
|
|
|
if (nb_try >= 1) {
|
|
|
|
|
// sleep before retrying
|
2022-04-20 22:39:52 +02:00
|
|
|
this.log.debug(checkpoint, "Sleep for " + timeToSleep / 1000 + "s before trying to re-upload the frame");
|
2022-03-12 12:43:10 +01:00
|
|
|
try {
|
2022-04-20 22:39:52 +02:00
|
|
|
Thread.sleep(timeToSleep);
|
2022-03-12 12:43:10 +01:00
|
|
|
}
|
|
|
|
|
catch (InterruptedException e) {
|
|
|
|
|
confirmJobReturnCode = Error.Type.UNKNOWN;
|
|
|
|
|
}
|
2022-04-20 22:39:52 +02:00
|
|
|
timeToSleep *= 2; // exponential backoff
|
2022-03-12 12:43:10 +01:00
|
|
|
}
|
|
|
|
|
|
2020-09-12 10:00:24 +10:00
|
|
|
ret = this.server.HTTPSendFile(url_real, ajob.getOutputImagePath(), checkpoint, this.gui);
|
2014-11-20 13:21:19 +00:00
|
|
|
switch (ret) {
|
|
|
|
|
case OK:
|
|
|
|
|
// no issue, exit the loop
|
2020-05-10 06:04:07 +10:00
|
|
|
break retryLoop;
|
2014-11-20 13:21:19 +00:00
|
|
|
|
|
|
|
|
case JOB_VALIDATION_ERROR_SESSION_DISABLED:
|
|
|
|
|
case JOB_VALIDATION_ERROR_BROKEN_MACHINE:
|
2020-05-10 06:04:07 +10:00
|
|
|
confirmJobReturnCode = Error.Type.SESSION_DISABLED;
|
|
|
|
|
break retryLoop;
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2020-10-10 10:52:39 +02:00
|
|
|
case JOB_VALIDATION_ERROR_IMAGE_WRONG_DIMENSION:
|
2020-10-27 14:42:59 +01:00
|
|
|
confirmJobReturnCode = Error.Type.IMAGE_WRONG_DIMENSION;
|
|
|
|
|
break retryLoop;
|
|
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
case JOB_VALIDATION_ERROR_MISSING_PARAMETER:
|
|
|
|
|
// no point to retry the request
|
2020-05-10 06:04:07 +10:00
|
|
|
confirmJobReturnCode = Error.Type.UNKNOWN;
|
|
|
|
|
break retryLoop;
|
2020-06-21 02:42:20 +10:00
|
|
|
|
|
|
|
|
case JOB_VALIDATION_IMAGE_TOO_LARGE:
|
|
|
|
|
// the client cannot recover from this error (it's server side config) so exit the retry loop
|
|
|
|
|
confirmJobReturnCode = Type.IMAGE_TOO_LARGE;
|
|
|
|
|
break retryLoop;
|
|
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
default:
|
|
|
|
|
// do nothing, try to do a request on the next loop
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
this.isValidatingJob = false;
|
2020-04-14 17:32:22 +02:00
|
|
|
this.previousJob = ajob;
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2021-12-21 10:02:32 +00:00
|
|
|
//count frames if they are not test frames and got validated correctly
|
2022-01-03 21:27:16 +00:00
|
|
|
if (confirmJobReturnCode == Error.Type.OK && Integer.parseInt(ajob.getId()) >= MIN_JOB_ID) {
|
2020-05-10 06:04:07 +10:00
|
|
|
gui.AddFrameRendered();
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
|
|
|
|
|
// we can remove the frame file
|
|
|
|
|
File frame = new File(ajob.getOutputImagePath());
|
|
|
|
|
frame.delete();
|
|
|
|
|
ajob.setOutputImagePath(null);
|
2020-05-28 13:28:42 +02:00
|
|
|
|
2020-05-10 06:04:07 +10:00
|
|
|
return confirmJobReturnCode;
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected boolean shouldWaitBeforeRender() {
|
|
|
|
|
int concurrent_job = this.jobsToValidate.size();
|
|
|
|
|
if (this.isValidatingJob) {
|
|
|
|
|
concurrent_job++;
|
|
|
|
|
}
|
2019-08-07 22:17:59 +02:00
|
|
|
return (concurrent_job >= this.configuration.getMaxUploadingJob());
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2020-06-18 21:57:52 +10:00
|
|
|
|
|
|
|
|
/****************
|
|
|
|
|
* Inner class that will hold the queued jobs. The constructor accepts two parameters:
|
|
|
|
|
* @int checkpoint - the checkpoint associated with the job (to add any additional log to the render output)
|
|
|
|
|
* @Job job - the job to be validated
|
|
|
|
|
*/
|
|
|
|
|
@AllArgsConstructor class QueuedJob {
|
|
|
|
|
final private int checkpoint;
|
|
|
|
|
final private Job job;
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|