2014-11-20 13:21:19 +00:00
|
|
|
/*
|
|
|
|
|
* Copyright (C) 2010-2014 Laurent CLOUET
|
|
|
|
|
* Author Laurent CLOUET <laurent.clouet@nopnop.net>
|
|
|
|
|
*
|
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
|
* as published by the Free Software Foundation; 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.BufferedReader;
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
|
import java.io.FileWriter;
|
|
|
|
|
import java.io.FilenameFilter;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.io.InputStreamReader;
|
|
|
|
|
import java.io.PrintWriter;
|
|
|
|
|
import java.io.StringWriter;
|
|
|
|
|
import java.text.DateFormat;
|
|
|
|
|
import java.text.ParseException;
|
|
|
|
|
import java.text.SimpleDateFormat;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Calendar;
|
|
|
|
|
import java.util.Date;
|
2015-01-16 17:51:50 +01:00
|
|
|
import java.util.HashMap;
|
2015-01-25 19:07:32 +00:00
|
|
|
import java.util.List;
|
2015-01-16 17:51:50 +01:00
|
|
|
import java.util.Map;
|
2015-01-28 00:46:08 +00:00
|
|
|
import java.util.TimeZone;
|
2014-11-20 13:21:19 +00:00
|
|
|
import java.util.concurrent.ArrayBlockingQueue;
|
|
|
|
|
import java.util.concurrent.BlockingQueue;
|
|
|
|
|
|
|
|
|
|
import com.sheepit.client.Error.ServerCode;
|
|
|
|
|
import com.sheepit.client.Error.Type;
|
|
|
|
|
import com.sheepit.client.exception.FermeException;
|
|
|
|
|
import com.sheepit.client.exception.FermeExceptionNoRightToRender;
|
|
|
|
|
import com.sheepit.client.exception.FermeExceptionNoSession;
|
|
|
|
|
import com.sheepit.client.exception.FermeExceptionSessionDisabled;
|
2015-01-24 17:52:37 +00:00
|
|
|
import com.sheepit.client.hardware.gpu.GPUDevice;
|
2014-11-20 13:21:19 +00:00
|
|
|
import com.sheepit.client.os.OS;
|
|
|
|
|
|
|
|
|
|
public class Client {
|
|
|
|
|
public static final String UPDATE_METHOD_BY_REMAINING_TIME = "remainingtime";
|
2015-01-13 21:10:31 +01:00
|
|
|
public static final String UPDATE_METHOD_BLENDER_INTERNAL_BY_PART = "blenderinternal";
|
2014-11-20 13:21:19 +00:00
|
|
|
|
|
|
|
|
private Gui gui;
|
|
|
|
|
private Server server;
|
|
|
|
|
private Configuration config;
|
|
|
|
|
private Log log;
|
|
|
|
|
private Job renderingJob;
|
|
|
|
|
private BlockingQueue<Job> jobsToValidate;
|
|
|
|
|
private boolean isValidatingJob;
|
|
|
|
|
|
|
|
|
|
private boolean disableErrorSending;
|
|
|
|
|
private boolean running;
|
2014-12-02 20:28:03 +00:00
|
|
|
private boolean suspended;
|
2014-11-20 13:21:19 +00:00
|
|
|
|
2015-05-05 18:35:25 +02:00
|
|
|
private int maxDownloadFileAttempts = 5;
|
|
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
public Client(Gui gui_, Configuration config, String url_) {
|
|
|
|
|
this.config = config;
|
|
|
|
|
this.server = new Server(url_, this.config, this);
|
|
|
|
|
this.log = Log.getInstance(this.config);
|
|
|
|
|
this.gui = gui_;
|
|
|
|
|
this.renderingJob = null;
|
|
|
|
|
this.jobsToValidate = new ArrayBlockingQueue<Job>(1024);
|
|
|
|
|
this.isValidatingJob = false;
|
|
|
|
|
|
|
|
|
|
this.disableErrorSending = false;
|
|
|
|
|
this.running = true;
|
2014-12-02 20:28:03 +00:00
|
|
|
this.suspended = false;
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String toString() {
|
|
|
|
|
return String.format("Client (config %s, server %s)", this.config, this.server);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Job getRenderingJob() {
|
|
|
|
|
return this.renderingJob;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Gui getGui() {
|
|
|
|
|
return this.gui;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Configuration getConfiguration() {
|
|
|
|
|
return this.config;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-15 23:20:17 +01:00
|
|
|
public Server getServer() {
|
|
|
|
|
return this.server;
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
public int run() {
|
2014-12-17 21:47:31 +00:00
|
|
|
if (this.config.checkOSisSupported() == false) {
|
|
|
|
|
this.gui.error(Error.humanString(Error.Type.OS_NOT_SUPPORTED));
|
|
|
|
|
return -3;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-05 23:04:38 +01:00
|
|
|
if (this.config.checkCPUisSupported() == false) {
|
2014-12-17 21:47:31 +00:00
|
|
|
this.gui.error(Error.humanString(Error.Type.CPU_NOT_SUPPORTED));
|
|
|
|
|
return -4;
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
int step;
|
|
|
|
|
try {
|
|
|
|
|
step = this.log.newCheckPoint();
|
|
|
|
|
this.gui.status("Starting");
|
|
|
|
|
|
|
|
|
|
this.config.cleanWorkingDirectory();
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
while (this.running == true) {
|
2015-03-24 19:42:56 +00:00
|
|
|
this.renderingJob = null;
|
2015-01-20 22:06:05 +01:00
|
|
|
synchronized (this) {
|
|
|
|
|
while (this.suspended) {
|
2014-12-02 20:28:03 +00:00
|
|
|
wait();
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00: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));
|
|
|
|
|
try {
|
|
|
|
|
Thread.sleep(next_request.getTimeInMillis() - now.getTime());
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e3) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (IllegalArgumentException e3) {
|
|
|
|
|
this.log.error("Client::run sleepA failed " + e3);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.gui.status("Requesting Job");
|
|
|
|
|
this.renderingJob = this.server.requestJob();
|
|
|
|
|
}
|
|
|
|
|
catch (FermeExceptionNoRightToRender e) {
|
2014-12-08 19:11:42 +00:00
|
|
|
this.gui.error("User does not have enough right to render scene");
|
2014-11-20 13:21:19 +00:00
|
|
|
return -2;
|
|
|
|
|
}
|
|
|
|
|
catch (FermeExceptionSessionDisabled e) {
|
2014-12-16 23:02:14 +00:00
|
|
|
this.gui.error(Error.humanString(Error.Type.SESSION_DISABLED));
|
2014-11-20 13:21:19 +00:00
|
|
|
// should wait forever to actually display the message to the user
|
|
|
|
|
while (true) {
|
|
|
|
|
try {
|
|
|
|
|
Thread.sleep(100000);
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e1) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (FermeExceptionNoSession e) {
|
2014-12-23 20:05:29 +01:00
|
|
|
// User has no session need to re-authenticate
|
2014-11-20 13:21:19 +00:00
|
|
|
|
|
|
|
|
ret = this.server.getConfiguration();
|
|
|
|
|
if (ret != Error.Type.OK) {
|
|
|
|
|
this.renderingJob = null;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (FermeException e) {
|
|
|
|
|
this.gui.error("Client::renderingManagement exception requestJob (1) " + e.getMessage());
|
2015-04-23 23:31:39 +01:00
|
|
|
StringWriter sw = new StringWriter();
|
|
|
|
|
PrintWriter pw = new PrintWriter(sw);
|
|
|
|
|
e.printStackTrace(pw);
|
|
|
|
|
this.log.debug("Client::run exception " + e + " stacktrace: " + sw.toString());
|
2014-11-20 13:21:19 +00:00
|
|
|
this.sendError(step);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.renderingJob == null) { // no job
|
|
|
|
|
int time_sleep = 1000 * 60 * 15;
|
|
|
|
|
Date wakeup_time = new Date(new Date().getTime() + time_sleep);
|
|
|
|
|
this.gui.status(String.format("No job available. Sleeping for 15 minutes (will wake up at ~%tR)", wakeup_time));
|
|
|
|
|
this.gui.framesRemaining(0);
|
|
|
|
|
int time_slept = 0;
|
|
|
|
|
while (time_slept < time_sleep && this.running == true) {
|
|
|
|
|
try {
|
|
|
|
|
Thread.sleep(5000);
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e) {
|
|
|
|
|
return -3;
|
|
|
|
|
}
|
|
|
|
|
time_slept += 5000;
|
|
|
|
|
}
|
|
|
|
|
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);
|
2014-12-03 20:09:50 +00:00
|
|
|
if (ret == Error.Type.RENDERER_KILLED) {
|
2015-01-05 23:05:29 +01:00
|
|
|
this.log.removeCheckPoint(step);
|
|
|
|
|
continue;
|
2014-12-03 20:09:50 +00:00
|
|
|
}
|
|
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
if (ret != Error.Type.OK) {
|
2014-12-23 20:05:29 +01:00
|
|
|
Job frame_to_reset = this.renderingJob; // copy it because the sendError will take ~5min to execute
|
2014-11-20 13:21:19 +00:00
|
|
|
this.renderingJob = null;
|
2014-12-16 23:02:14 +00:00
|
|
|
this.gui.error(Error.humanString(ret));
|
2014-11-20 13:21:19 +00:00
|
|
|
this.sendError(step, frame_to_reset, ret);
|
|
|
|
|
this.log.removeCheckPoint(step);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.renderingJob.simultaneousUploadIsAllowed() == false) { // power or compute_method job, need to upload right away
|
|
|
|
|
ret = confirmJob(this.renderingJob);
|
|
|
|
|
if (ret != Error.Type.OK) {
|
|
|
|
|
gui.error("Client::renderingManagement problem with confirmJob (returned " + ret + ")");
|
|
|
|
|
sendError(step);
|
|
|
|
|
}
|
2014-12-11 13:40:18 +00:00
|
|
|
else {
|
|
|
|
|
gui.AddFrameRendered();
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this.jobsToValidate.add(this.renderingJob);
|
|
|
|
|
this.renderingJob = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (this.shouldWaitBeforeRender() == true) {
|
|
|
|
|
try {
|
|
|
|
|
Thread.sleep(4000); // wait a little bit
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e3) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.log.removeCheckPoint(step);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// not running but maybe still sending frame
|
2015-01-28 20:14:59 +00:00
|
|
|
while (this.jobsToValidate.isEmpty() == false) {
|
2014-11-20 13:21:19 +00:00
|
|
|
try {
|
|
|
|
|
Thread.sleep(2300); // wait a little bit
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e3) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public synchronized int stop() {
|
|
|
|
|
this.running = false;
|
|
|
|
|
this.disableErrorSending = true;
|
|
|
|
|
|
|
|
|
|
if (this.renderingJob != null) {
|
2015-04-27 20:10:36 +01:00
|
|
|
if (this.renderingJob.getProcessRender().getProcess() != null) {
|
|
|
|
|
OS.getOS().kill(this.renderingJob.getProcessRender().getProcess());
|
2014-12-03 20:09:50 +00:00
|
|
|
this.renderingJob.setAskForRendererKill(true);
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// this.config.workingDirectory.delete();
|
|
|
|
|
this.config.removeWorkingDirectory();
|
|
|
|
|
|
|
|
|
|
if (this.server == null) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
this.server.HTTPRequest(this.server.getPage("logout"));
|
|
|
|
|
}
|
|
|
|
|
catch (IOException e) {
|
|
|
|
|
// nothing to do: if the logout failed that's ok
|
|
|
|
|
}
|
|
|
|
|
this.server.interrupt();
|
|
|
|
|
try {
|
|
|
|
|
this.server.join();
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.server = null;
|
|
|
|
|
|
|
|
|
|
this.gui.stop();
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-17 22:40:24 +01:00
|
|
|
public boolean isSuspended() {
|
|
|
|
|
return this.suspended;
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-02 20:28:03 +00:00
|
|
|
public void suspend() {
|
|
|
|
|
suspended = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public synchronized void resume() {
|
|
|
|
|
suspended = false;
|
|
|
|
|
notify();
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
public void askForStop() {
|
|
|
|
|
System.out.println("Client::askForStop");
|
|
|
|
|
this.running = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int senderLoop() {
|
|
|
|
|
int step = log.newCheckPoint();
|
|
|
|
|
Error.Type ret;
|
|
|
|
|
while (true) {
|
|
|
|
|
Job job_to_send;
|
|
|
|
|
try {
|
2015-01-23 17:18:17 +00:00
|
|
|
job_to_send = jobsToValidate.take();
|
2014-11-20 13:21:19 +00:00
|
|
|
this.log.debug("will validate " + job_to_send);
|
|
|
|
|
//gui.status("Sending frame");
|
|
|
|
|
ret = confirmJob(job_to_send);
|
|
|
|
|
if (ret != Error.Type.OK) {
|
2014-12-16 23:02:14 +00:00
|
|
|
this.gui.error(Error.humanString(ret));
|
2015-04-23 23:31:39 +01:00
|
|
|
this.log.debug("Client::senderLoop confirm failed, ret: " + ret);
|
2014-11-20 13:21:19 +00:00
|
|
|
sendError(step);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
gui.AddFrameRendered();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
File temp_file = File.createTempFile("farm_", "");
|
|
|
|
|
temp_file.createNewFile();
|
|
|
|
|
temp_file.deleteOnExit();
|
|
|
|
|
FileOutputStream writer = new FileOutputStream(temp_file);
|
|
|
|
|
|
|
|
|
|
ArrayList<String> logs = this.log.getForCheckPoint(step_);
|
|
|
|
|
for (String line : logs) {
|
|
|
|
|
writer.write(line.getBytes());
|
|
|
|
|
writer.write('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
writer.close();
|
2015-03-11 17:55:44 +00:00
|
|
|
String args = "?type=" + (error == null ? "" : error.getValue());
|
2014-11-20 13:21:19 +00:00
|
|
|
if (job_to_reset_ != null) {
|
2015-04-27 20:10:36 +01:00
|
|
|
args += "&frame=" + job_to_reset_.getFrameNumber() + "&job=" + job_to_reset_.getId() + "&render_time=" + job_to_reset_.getProcessRender().getDuration();
|
2015-01-28 20:14:59 +00:00
|
|
|
if (job_to_reset_.getExtras() != null && job_to_reset_.getExtras().isEmpty() == false) {
|
2014-11-20 13:21:19 +00:00
|
|
|
args += "&extras=" + job_to_reset_.getExtras();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.server.HTTPSendFile(this.server.getPage("error") + args, temp_file.getAbsolutePath());
|
|
|
|
|
temp_file.delete();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e1) {
|
|
|
|
|
e1.printStackTrace();
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (error != null && error == Error.Type.RENDERER_CRASHED) {
|
|
|
|
|
// 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() {
|
|
|
|
|
if (this.config.requestTime == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
Calendar next = null;
|
|
|
|
|
Calendar now = Calendar.getInstance();
|
|
|
|
|
for (Pair<Calendar, Calendar> interval : this.config.requestTime) {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
if (start.after(now)) {
|
|
|
|
|
if (next == null) {
|
|
|
|
|
next = start;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (start.before(next)) {
|
|
|
|
|
next = start;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Error.Type work(Job ajob) {
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
ret = this.downloadExecutable(ajob);
|
|
|
|
|
if (ret != 0) {
|
|
|
|
|
this.log.error("Client::work problem with downloadExecutable (ret " + ret + ")");
|
|
|
|
|
return Error.Type.DOWNLOAD_FILE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = this.downloadSceneFile(ajob);
|
|
|
|
|
if (ret != 0) {
|
|
|
|
|
this.log.error("Client::work problem with downloadSceneFile (ret " + ret + ")");
|
|
|
|
|
return Error.Type.DOWNLOAD_FILE;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-18 16:50:28 +01:00
|
|
|
ret = this.prepareWorkingDirectory(ajob); // decompress renderer and scene archives
|
2014-11-20 13:21:19 +00:00
|
|
|
if (ret != 0) {
|
2015-01-18 16:50:28 +01:00
|
|
|
this.log.error("Client::work problem with this.prepareWorkingDirectory (ret " + ret + ")");
|
2014-11-20 13:21:19 +00:00
|
|
|
return Error.Type.CAN_NOT_CREATE_DIRECTORY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
File scene_file = new File(ajob.getScenePath());
|
|
|
|
|
File renderer_file = new File(ajob.getRendererPath());
|
|
|
|
|
|
|
|
|
|
if (scene_file.exists() == false) {
|
2014-12-16 23:02:14 +00:00
|
|
|
this.log.error("Client::work job preparation failed (scene file '" + scene_file.getAbsolutePath() + "' does not exist)");
|
2014-11-20 13:21:19 +00:00
|
|
|
return Error.Type.MISSING_SCENE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (renderer_file.exists() == false) {
|
2014-12-16 23:02:14 +00:00
|
|
|
this.log.error("Client::work job preparation failed (renderer file '" + renderer_file.getAbsolutePath() + "' does not exist)");
|
2014-11-20 13:21:19 +00:00
|
|
|
return Error.Type.MISSING_RENDER;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Error.Type err = this.runRenderer(ajob);
|
|
|
|
|
if (err != Error.Type.OK) {
|
|
|
|
|
this.log.error("Client::work problem with runRenderer (ret " + err + ")");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Error.Type.OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected Error.Type runRenderer(Job ajob) {
|
|
|
|
|
this.gui.status("Rendering");
|
2015-04-27 20:10:36 +01:00
|
|
|
RenderProcess process = ajob.getProcessRender();
|
2015-03-24 21:41:12 +00:00
|
|
|
String core_script = "import bpy\n" + "bpy.context.user_preferences.system.compute_device_type = \"%s\"\n" + "bpy.context.scene.cycles.device = \"%s\"\n" + "bpy.context.user_preferences.system.compute_device = \"%s\"\n";
|
2014-11-20 13:21:19 +00:00
|
|
|
if (ajob.getUseGPU() && this.config.getGPUDevice() != null) {
|
2015-03-24 21:41:12 +00:00
|
|
|
core_script = String.format(core_script, "CUDA", "GPU", this.config.getGPUDevice().getCudaName());
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
else {
|
2015-03-24 21:41:12 +00:00
|
|
|
core_script = String.format(core_script, "NONE", "CPU", "CPU");
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2015-03-24 21:41:12 +00:00
|
|
|
core_script += String.format("bpy.context.scene.render.tile_x = %1$d\nbpy.context.scene.render.tile_y = %1$d\n", this.getTileSize(ajob));
|
2014-11-20 13:21:19 +00:00
|
|
|
File script_file = null;
|
|
|
|
|
String command1[] = ajob.getRenderCommand().split(" ");
|
|
|
|
|
int size_command = command1.length + 2; // + 2 for script
|
|
|
|
|
|
2014-12-23 20:05:29 +01:00
|
|
|
if (this.config.getNbCores() > 0) { // user has specified something
|
2014-11-20 13:21:19 +00:00
|
|
|
size_command += 2;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-25 19:07:32 +00:00
|
|
|
List<String> command = new ArrayList<String>(size_command);
|
2014-11-20 13:21:19 +00:00
|
|
|
|
2015-01-16 17:51:50 +01:00
|
|
|
Map<String, String> new_env = new HashMap<String, String>();
|
|
|
|
|
|
|
|
|
|
new_env.put("BLENDER_USER_CONFIG", this.config.workingDirectory.getAbsolutePath().replace("\\", "\\\\"));
|
2015-01-20 22:06:05 +01:00
|
|
|
|
2015-01-25 18:36:39 +00:00
|
|
|
for (String arg : command1) {
|
2015-01-28 20:11:57 +00:00
|
|
|
switch (arg) {
|
|
|
|
|
case ".c":
|
|
|
|
|
command.add(ajob.getScenePath());
|
|
|
|
|
command.add("-P");
|
2014-11-20 13:21:19 +00:00
|
|
|
|
2015-01-28 20:11:57 +00:00
|
|
|
try {
|
|
|
|
|
script_file = File.createTempFile("script_", "", this.config.workingDirectory);
|
|
|
|
|
File file = new File(script_file.getAbsolutePath());
|
|
|
|
|
FileWriter txt;
|
|
|
|
|
txt = new FileWriter(file);
|
|
|
|
|
|
|
|
|
|
PrintWriter out = new PrintWriter(txt);
|
|
|
|
|
out.write(ajob.getScript());
|
|
|
|
|
out.write("\n");
|
|
|
|
|
out.write(core_script); // GPU part
|
|
|
|
|
out.write("\n"); // GPU part
|
|
|
|
|
out.close();
|
|
|
|
|
|
|
|
|
|
command.add(script_file.getAbsolutePath());
|
|
|
|
|
}
|
|
|
|
|
catch (IOException e) {
|
2015-04-30 20:32:24 +01:00
|
|
|
StringWriter sw = new StringWriter();
|
|
|
|
|
e.printStackTrace(new PrintWriter(sw));
|
|
|
|
|
this.log.error("Client:runRenderer exception on script generation, will return UNKNOWN " + e + " stacktrace " + sw.toString());
|
2015-01-28 20:11:57 +00:00
|
|
|
return Error.Type.UNKNOWN;
|
|
|
|
|
}
|
|
|
|
|
script_file.deleteOnExit();
|
|
|
|
|
break;
|
|
|
|
|
case ".e":
|
|
|
|
|
command.add(ajob.getRendererPath());
|
|
|
|
|
// the number of cores has to be put after the binary and before the scene arg
|
|
|
|
|
if (this.config.getNbCores() > 0) {
|
|
|
|
|
command.add("-t");
|
|
|
|
|
command.add(Integer.toString(this.config.getNbCores()));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case ".o":
|
|
|
|
|
command.add(this.config.workingDirectory.getAbsolutePath() + File.separator + ajob.getPrefixOutputImage());
|
|
|
|
|
break;
|
|
|
|
|
case ".f":
|
|
|
|
|
command.add(ajob.getFrameNumber());
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
command.add(arg);
|
|
|
|
|
break;
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
String line;
|
2015-01-25 19:07:32 +00:00
|
|
|
this.log.debug(command.toString());
|
2014-11-20 13:21:19 +00:00
|
|
|
OS os = OS.getOS();
|
2015-04-27 20:10:36 +01:00
|
|
|
process.start();
|
|
|
|
|
ajob.getProcessRender().setProcess(os.exec(command, new_env));
|
|
|
|
|
BufferedReader input = new BufferedReader(new InputStreamReader(ajob.getProcessRender().getProcess().getInputStream()));
|
2014-11-20 13:21:19 +00:00
|
|
|
|
|
|
|
|
long last_update_status = 0;
|
|
|
|
|
this.log.debug("renderer output");
|
2014-12-02 20:23:41 +00:00
|
|
|
try {
|
|
|
|
|
while ((line = input.readLine()) != null) {
|
|
|
|
|
this.updateRenderingMemoryPeak(line, ajob);
|
|
|
|
|
|
2014-12-03 20:07:54 +00:00
|
|
|
this.log.debug(line);
|
2014-12-02 20:23:41 +00:00
|
|
|
if ((new Date().getTime() - last_update_status) > 2000) { // only call the update every two seconds
|
2015-04-23 21:56:57 +01:00
|
|
|
this.updateRenderingStatus(line, ajob);
|
2014-12-02 20:23:41 +00:00
|
|
|
last_update_status = new Date().getTime();
|
|
|
|
|
}
|
2015-03-10 20:21:50 +00:00
|
|
|
Type error = this.detectError(line, ajob);
|
|
|
|
|
if (error != Error.Type.OK) {
|
|
|
|
|
if (script_file != null) {
|
|
|
|
|
script_file.delete();
|
|
|
|
|
}
|
|
|
|
|
return error;
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
2014-12-02 20:23:41 +00:00
|
|
|
input.close();
|
|
|
|
|
}
|
|
|
|
|
catch (IOException err1) { // for the input.readline
|
|
|
|
|
// most likely The handle is invalid
|
|
|
|
|
this.log.error("Client:runRenderer exception(B) (silent error) " + err1);
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
this.log.debug("end of rendering");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception err) {
|
2015-03-10 20:20:37 +00:00
|
|
|
if (script_file != null) {
|
|
|
|
|
script_file.delete();
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
StringWriter sw = new StringWriter();
|
|
|
|
|
err.printStackTrace(new PrintWriter(sw));
|
|
|
|
|
this.log.error("Client:runRenderer exception(A) " + err + " stacktrace " + sw.toString());
|
|
|
|
|
return Error.Type.FAILED_TO_EXECUTE;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-27 20:10:36 +01:00
|
|
|
int exit_value = process.exitValue();
|
|
|
|
|
process.finish();
|
2014-11-20 13:21:19 +00:00
|
|
|
|
|
|
|
|
if (script_file != null) {
|
|
|
|
|
script_file.delete();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// find the picture file
|
2015-01-05 23:04:38 +01:00
|
|
|
final String filename_without_extension = ajob.getPrefixOutputImage() + ajob.getFrameNumber();
|
2014-11-20 13:21:19 +00:00
|
|
|
|
|
|
|
|
FilenameFilter textFilter = new FilenameFilter() {
|
|
|
|
|
public boolean accept(File dir, String name) {
|
2015-01-05 23:04:38 +01:00
|
|
|
return name.startsWith(filename_without_extension);
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
File[] files = this.config.workingDirectory.listFiles(textFilter);
|
|
|
|
|
|
|
|
|
|
if (files.length == 0) {
|
2015-01-05 23:04:38 +01:00
|
|
|
this.log.error("Client::runRenderer no picture file found (after finished render (filename_without_extension " + filename_without_extension + ")");
|
2014-11-20 13:21:19 +00:00
|
|
|
|
2014-12-03 20:09:50 +00:00
|
|
|
if (ajob.getAskForRendererKill()) {
|
2015-01-05 23:05:29 +01:00
|
|
|
this.log.debug("Client::runRenderer renderer didn't generate any frame but died due to a kill request");
|
|
|
|
|
return Error.Type.RENDERER_KILLED;
|
2014-12-03 20:09:50 +00:00
|
|
|
}
|
|
|
|
|
|
2014-11-20 13:21:19 +00:00
|
|
|
String basename = "";
|
|
|
|
|
try {
|
|
|
|
|
basename = ajob.getPath().substring(0, ajob.getPath().lastIndexOf('.'));
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
File crash_file = new File(this.config.workingDirectory + File.separator + basename + ".crash.txt");
|
|
|
|
|
if (crash_file.exists()) {
|
|
|
|
|
this.log.error("Client::runRenderer crash file found => the renderer crashed");
|
|
|
|
|
crash_file.delete();
|
|
|
|
|
return Error.Type.RENDERER_CRASHED;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-27 20:10:36 +01:00
|
|
|
if (exit_value == 127 && process.getDuration() < 10) {
|
|
|
|
|
this.log.error("Client::runRenderer renderer returned 127 and took " + process.getDuration() + "s, some libraries may be missing");
|
2014-11-20 13:21:19 +00:00
|
|
|
return Error.Type.RENDERER_MISSING_LIBRARIES;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Error.Type.NOOUTPUTFILE;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
ajob.setOutputImagePath(files[0].getAbsolutePath());
|
|
|
|
|
this.log.debug("Client::runRenderer pictureFilename: '" + ajob.getOutputImagePath() + "'");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
File scene_dir = new File(ajob.getSceneDirectory());
|
|
|
|
|
long date_modification_scene_directory = (long) Utils.lastModificationTime(scene_dir);
|
2015-04-27 20:10:36 +01:00
|
|
|
if (date_modification_scene_directory > process.getStartTime()) {
|
2014-11-20 13:21:19 +00:00
|
|
|
scene_dir.delete();
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-27 20:10:36 +01:00
|
|
|
this.gui.status(String.format("Frame rendered in %dmin%ds", process.getDuration() / 60, process.getDuration() % 60));
|
2014-11-20 13:21:19 +00:00
|
|
|
|
|
|
|
|
return Error.Type.OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected int downloadSceneFile(Job ajob_) {
|
2015-01-27 21:47:33 +00:00
|
|
|
this.gui.status("Downloading project");
|
2015-04-28 18:46:04 +01:00
|
|
|
return this.downloadFile(ajob_, ajob_.getSceneArchivePath(), ajob_.getSceneMD5(), String.format("%s?type=job&job=%s&revision=%s", this.server.getPage("download-archive"), ajob_.getId(), ajob_.getRevision()), "Downloading project %s %%");
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected int downloadExecutable(Job ajob) {
|
|
|
|
|
this.gui.status("Downloading renderer");
|
2015-04-28 18:46:04 +01:00
|
|
|
return this.downloadFile(ajob, ajob.getRendererArchivePath(), ajob.getRenderMd5(), String.format("%s?type=binary&job=%s", this.server.getPage("download-archive"), ajob.getId()), "Downloading renderer %s %%");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int downloadFile(Job ajob, String local_path, String md5_server, String url, String update_ui) {
|
|
|
|
|
File local_path_file = new File(local_path);
|
2014-11-20 13:21:19 +00:00
|
|
|
|
2015-05-05 18:35:25 +02:00
|
|
|
if (local_path_file.exists() == true) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// must download the archive
|
|
|
|
|
int ret = this.server.HTTPGetFile(url, local_path, this.gui, update_ui);
|
|
|
|
|
boolean md5_check = this.checkFile(ajob, local_path, md5_server);
|
|
|
|
|
int attempts = 1;
|
|
|
|
|
|
|
|
|
|
while ((ret != 0 || md5_check == false) && attempts < this.maxDownloadFileAttempts) {
|
2014-11-20 13:21:19 +00:00
|
|
|
if (ret != 0) {
|
2015-04-28 18:46:04 +01:00
|
|
|
this.gui.error("Client::downloadFile problem with Utils.HTTPGetFile returned " + ret);
|
2015-04-29 21:21:17 +01:00
|
|
|
this.log.debug("Client::downloadFile problem with Utils.HTTPGetFile (return: " + ret + ") removing local file (path: " + local_path + ")");
|
2015-05-05 18:35:25 +02:00
|
|
|
}
|
|
|
|
|
else if (md5_check == false) {
|
|
|
|
|
this.gui.error("Client::downloadFile problem with Client::checkFile mismatch on md5");
|
|
|
|
|
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);
|
|
|
|
|
md5_check = this.checkFile(ajob, local_path, md5_server);
|
|
|
|
|
attempts++;
|
|
|
|
|
|
|
|
|
|
if ((ret != 0 || md5_check == false) && attempts >= this.maxDownloadFileAttempts) {
|
|
|
|
|
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();
|
2014-11-20 13:21:19 +00:00
|
|
|
return -9;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-05 18:35:25 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
2015-05-06 20:00:52 +01: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
|
|
|
}
|
|
|
|
|
|
2015-01-18 16:50:28 +01:00
|
|
|
protected int prepareWorkingDirectory(Job ajob) {
|
2014-11-20 13:21:19 +00:00
|
|
|
int ret;
|
|
|
|
|
String renderer_archive = ajob.getRendererArchivePath();
|
|
|
|
|
String renderer_path = ajob.getRendererDirectory();
|
|
|
|
|
File renderer_path_file = new File(renderer_path);
|
|
|
|
|
|
|
|
|
|
if (renderer_path_file.exists()) {
|
|
|
|
|
// Directory already exists -> do nothing
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// we create the directory
|
|
|
|
|
renderer_path_file.mkdir();
|
|
|
|
|
|
|
|
|
|
// unzip the archive
|
|
|
|
|
ret = Utils.unzipFileIntoDirectory(renderer_archive, renderer_path);
|
|
|
|
|
if (ret != 0) {
|
2015-01-18 16:50:28 +01:00
|
|
|
this.gui.error("Client::prepareWorkingDirectory, error with Utils.unzipFileIntoDirectory of the renderer (returned " + ret + ")");
|
2014-11-20 13:21:19 +00:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String scene_archive = ajob.getSceneArchivePath();
|
|
|
|
|
String scene_path = ajob.getSceneDirectory();
|
|
|
|
|
File scene_path_file = new File(scene_path);
|
|
|
|
|
|
|
|
|
|
if (scene_path_file.exists()) {
|
|
|
|
|
// Directory already exists -> do nothing
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// we create the directory
|
|
|
|
|
scene_path_file.mkdir();
|
|
|
|
|
|
|
|
|
|
// unzip the archive
|
|
|
|
|
ret = Utils.unzipFileIntoDirectory(scene_archive, scene_path);
|
|
|
|
|
if (ret != 0) {
|
2015-01-18 16:50:28 +01:00
|
|
|
this.gui.error("Client::prepareWorkingDirectory, error with Utils.unzipFileIntoDirectory of the scene (returned " + ret + ")");
|
2014-11-20 13:21:19 +00:00
|
|
|
return -2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected Error.Type confirmJob(Job ajob) {
|
|
|
|
|
String extras_config = "";
|
|
|
|
|
if (this.config.getNbCores() > 0) {
|
|
|
|
|
extras_config = "&cores=" + this.config.getNbCores();
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-27 20:10:36 +01:00
|
|
|
String url_real = String.format("%s?job=%s&frame=%s&rendertime=%d&revision=%s&memoryused=%s&extras=%s%s", this.server.getPage("validate-job"), ajob.getId(), ajob.getFrameNumber(), ajob.getProcessRender().getDuration(), ajob.getRevision(), ajob.getProcessRender().getMemoryUsed(), ajob.getExtras(), extras_config);
|
2014-11-20 13:21:19 +00:00
|
|
|
|
|
|
|
|
this.isValidatingJob = true;
|
|
|
|
|
int nb_try = 1;
|
|
|
|
|
int max_try = 3;
|
|
|
|
|
ServerCode ret = ServerCode.UNKNOWN;
|
|
|
|
|
while (nb_try < max_try && ret != ServerCode.OK) {
|
|
|
|
|
ret = this.server.HTTPSendFile(url_real, ajob.getOutputImagePath());
|
|
|
|
|
switch (ret) {
|
|
|
|
|
case OK:
|
|
|
|
|
// no issue, exit the loop
|
|
|
|
|
nb_try = max_try;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case JOB_VALIDATION_ERROR_SESSION_DISABLED:
|
|
|
|
|
case JOB_VALIDATION_ERROR_BROKEN_MACHINE:
|
|
|
|
|
return Type.SESSION_DISABLED;
|
|
|
|
|
|
|
|
|
|
case JOB_VALIDATION_ERROR_MISSING_PARAMETER:
|
|
|
|
|
// no point to retry the request
|
|
|
|
|
return Error.Type.UNKNOWN;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
// do nothing, try to do a request on the next loop
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nb_try++;
|
|
|
|
|
if (ret != ServerCode.OK && nb_try < max_try) {
|
|
|
|
|
try {
|
|
|
|
|
this.log.debug("Sleep for 32s before trying to re-upload the frame");
|
|
|
|
|
Thread.sleep(32000);
|
|
|
|
|
}
|
|
|
|
|
catch (InterruptedException e) {
|
|
|
|
|
return Error.Type.UNKNOWN;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.isValidatingJob = false;
|
|
|
|
|
|
|
|
|
|
// we can remove the frame file
|
|
|
|
|
File frame = new File(ajob.getOutputImagePath());
|
|
|
|
|
frame.delete();
|
|
|
|
|
ajob.setOutputImagePath(null);
|
|
|
|
|
|
|
|
|
|
this.isValidatingJob = false;
|
|
|
|
|
return Error.Type.OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected boolean shouldWaitBeforeRender() {
|
|
|
|
|
int concurrent_job = this.jobsToValidate.size();
|
|
|
|
|
if (this.isValidatingJob) {
|
|
|
|
|
concurrent_job++;
|
|
|
|
|
}
|
|
|
|
|
return (concurrent_job >= this.config.maxUploadingJob());
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-23 21:56:57 +01:00
|
|
|
protected void updateRenderingStatus(String line, Job ajob) {
|
|
|
|
|
if (ajob.getUpdateRenderingStatusMethod() != null && ajob.getUpdateRenderingStatusMethod().equals(Client.UPDATE_METHOD_BLENDER_INTERNAL_BY_PART)) {
|
2015-01-13 21:10:31 +01:00
|
|
|
String search = " Part ";
|
|
|
|
|
int index = line.lastIndexOf(search);
|
|
|
|
|
if (index != -1) {
|
|
|
|
|
String buf = line.substring(index + search.length());
|
|
|
|
|
String[] parts = buf.split("-");
|
|
|
|
|
if (parts != null && parts.length == 2) {
|
|
|
|
|
try {
|
2015-01-20 22:06:05 +01:00
|
|
|
int current = Integer.parseInt(parts[0]);
|
2015-01-13 21:10:31 +01:00
|
|
|
int total = Integer.parseInt(parts[1]);
|
|
|
|
|
if (total != 0) {
|
|
|
|
|
this.gui.status(String.format("Rendering %s %%", (int) (100.0 * current / total)));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (NumberFormatException e) {
|
|
|
|
|
System.out.println("Exception 92: " + e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.gui.status("Rendering");
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
else if (ajob.getUpdateRenderingStatusMethod() == null || ajob.getUpdateRenderingStatusMethod().equals(Client.UPDATE_METHOD_BY_REMAINING_TIME)) {
|
|
|
|
|
String search_remaining = "remaining:";
|
|
|
|
|
int index = line.toLowerCase().indexOf(search_remaining);
|
|
|
|
|
if (index != -1) {
|
|
|
|
|
String buf1 = line.substring(index + search_remaining.length());
|
|
|
|
|
index = buf1.indexOf(" ");
|
|
|
|
|
|
|
|
|
|
if (index != -1) {
|
|
|
|
|
String remaining_time = buf1.substring(0, index).trim();
|
|
|
|
|
int last_index = remaining_time.lastIndexOf('.'); //format 00:00:00.00 (hr:min:sec)
|
|
|
|
|
if (last_index > 0) {
|
|
|
|
|
remaining_time = remaining_time.substring(0, last_index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
DateFormat date_parse_minute = new SimpleDateFormat("m:s");
|
|
|
|
|
DateFormat date_parse_hour = new SimpleDateFormat("h:m:s");
|
|
|
|
|
DateFormat date_parse = date_parse_minute;
|
|
|
|
|
if (remaining_time.split(":").length > 2) {
|
|
|
|
|
date_parse = date_parse_hour;
|
|
|
|
|
}
|
2015-01-28 00:46:08 +00:00
|
|
|
date_parse.setTimeZone(TimeZone.getTimeZone("GMT"));
|
2015-04-27 20:35:44 +01:00
|
|
|
Date date = date_parse.parse(remaining_time);
|
|
|
|
|
this.gui.status(String.format("Rendering (remaining %s)", Utils.humanDuration(date)));
|
|
|
|
|
ajob.getProcessRender().setRemainingDuration((int) (date.getTime() / 1000));
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
catch (ParseException err) {
|
|
|
|
|
this.log.error("Client::updateRenderingStatus ParseException " + err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void updateRenderingMemoryPeak(String line, Job ajob) {
|
|
|
|
|
String[] elements = line.toLowerCase().split("(peak)");
|
|
|
|
|
|
|
|
|
|
for (String element : elements) {
|
|
|
|
|
if (element.isEmpty() == false && element.charAt(0) == ' ') {
|
|
|
|
|
int end = element.indexOf(')');
|
|
|
|
|
if (end > 0) {
|
|
|
|
|
long mem = Utils.parseNumber(element.substring(1, end).trim());
|
2015-04-27 20:10:36 +01:00
|
|
|
if (mem > ajob.getProcessRender().getMemoryUsed()) {
|
|
|
|
|
ajob.getProcessRender().setMemoryUsed(mem);
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (element.isEmpty() == false && element.charAt(0) == ':') {
|
|
|
|
|
int end = element.indexOf('|');
|
|
|
|
|
if (end > 0) {
|
|
|
|
|
long mem = Utils.parseNumber(element.substring(1, end).trim());
|
2015-04-27 20:10:36 +01:00
|
|
|
if (mem > ajob.getProcessRender().getMemoryUsed()) {
|
|
|
|
|
ajob.getProcessRender().setMemoryUsed(mem);
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-01-24 17:52:37 +00:00
|
|
|
|
2015-03-10 20:21:50 +00:00
|
|
|
private Type detectError(String line, Job ajob) {
|
|
|
|
|
|
|
|
|
|
if (line.indexOf("CUDA error: Out of memory") != -1) {
|
|
|
|
|
// Fra:151 Mem:405.91M (0.00M, Peak 633.81M) | Mem:470.26M, Peak:470.26M | Scene, RenderLayer | Updating Device | Writing constant memory
|
|
|
|
|
// Fra:151 Mem:405.91M (0.00M, Peak 633.81M) | Mem:470.26M, Peak:470.26M | Scene, RenderLayer | Path Tracing Tile 0/135, Sample 0/200
|
|
|
|
|
// Fra:151 Mem:405.91M (0.00M, Peak 633.81M) | Mem:470.82M, Peak:470.82M | Scene, RenderLayer | Path Tracing Tile 1/135, Sample 0/200
|
|
|
|
|
// CUDA error: Out of memory in cuLaunchKernel(cuPathTrace, xblocks , yblocks, 1, xthreads, ythreads, 1, 0, 0, args, 0)
|
|
|
|
|
// Refer to the Cycles GPU rendering documentation for possible solutions:
|
|
|
|
|
// http://www.blender.org/manual/render/cycles/gpu_rendering.html
|
|
|
|
|
// Fra:151 Mem:405.91M (0.00M, Peak 633.81M) | Remaining:09:26.57 | Mem:470.26M, Peak:470.82M | Scene, RenderLayer | Path Tracing Tile 1/135, Sample 200/200
|
|
|
|
|
// Fra:151 Mem:405.91M (0.00M, Peak 633.81M) | Remaining:00:00.06 | Mem:470.50M, Peak:470.82M | Scene, RenderLayer | Path Tracing Tile 134/135, Sample 0/200
|
|
|
|
|
// Fra:151 Mem:405.91M (0.00M, Peak 633.81M) | Remaining:00:00.03 | Mem:470.26M, Peak:470.82M | Scene, RenderLayer | Path Tracing Tile 134/135, Sample 200/200
|
|
|
|
|
// Fra:151 Mem:405.91M (0.00M, Peak 633.81M) | Remaining:00:00.03 | Mem:470.50M, Peak:470.82M | Scene, RenderLayer | Path Tracing Tile 135/135, Sample 0/200
|
|
|
|
|
// Fra:151 Mem:405.91M (0.00M, Peak 633.81M) | Mem:470.26M, Peak:470.82M | Scene, RenderLayer | Path Tracing Tile 135/135, Sample 200/200
|
|
|
|
|
// Error: CUDA error: Out of memory in cuLaunchKernel(cuPathTrace, xblocks , yblocks, 1, xthreads, ythreads, 1, 0, 0, args, 0)
|
|
|
|
|
// Fra:151 Mem:405.91M (0.00M, Peak 633.81M) | Mem:470.26M, Peak:470.82M | Scene, RenderLayer | Cancel | CUDA error: Out of memory in cuLaunchKernel(cuPathTrace, xblocks , yblocks, 1, xthreads, ythreads, 1, 0, 0, args, 0)
|
|
|
|
|
// Fra:151 Mem:405.89M (0.00M, Peak 633.81M) Sce: Scene Ve:0 Fa:0 La:0
|
|
|
|
|
// Saved: /tmp/xx/26885_0151.png Time: 00:04.67 (Saving: 00:00.22)
|
|
|
|
|
// Blender quit
|
|
|
|
|
return Type.RENDERER_OUT_OF_VIDEO_MEMORY;
|
|
|
|
|
}
|
|
|
|
|
else if (line.indexOf("CUDA device supported only with compute capability") != -1) {
|
|
|
|
|
// found bundled python: /tmp/xx/2.73/python
|
|
|
|
|
// read blend: /tmp/xx/compute-method.blend
|
|
|
|
|
// Fra:340 Mem:7.64M (0.00M, Peak 8.23M) | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Synchronizing object | Sun
|
|
|
|
|
// Fra:340 Mem:7.64M (0.00M, Peak 8.23M) | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Synchronizing object | Plane
|
|
|
|
|
// Fra:340 Mem:7.64M (0.00M, Peak 8.23M) | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Synchronizing object | Cube
|
|
|
|
|
// Fra:340 Mem:7.64M (0.00M, Peak 8.23M) | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Synchronizing object | Camera
|
|
|
|
|
// Fra:340 Mem:7.64M (0.00M, Peak 8.23M) | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Initializing
|
|
|
|
|
// Fra:340 Mem:7.64M (0.00M, Peak 8.23M) | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Loading render kernels (may take a few minutes the first time)
|
|
|
|
|
// CUDA device supported only with compute capability 2.0 or up, found 1.2.
|
|
|
|
|
// Refer to the Cycles GPU rendering documentation for possible solutions:
|
|
|
|
|
// http://www.blender.org/manual/render/cycles/gpu_rendering.html
|
|
|
|
|
// Fra:340 Mem:7.64M (0.00M, Peak 8.23M) | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Error | CUDA device supported only with compute capability 2.0 or up, found 1.2.
|
|
|
|
|
// Error: CUDA device supported only with compute capability 2.0 or up, found 1.2.
|
|
|
|
|
// Fra:340 Mem:7.64M (0.00M, Peak 8.23M) | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Waiting for render to start
|
|
|
|
|
// Fra:340 Mem:7.64M (0.00M, Peak 8.23M) | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Cancel | CUDA device supported only with compute capability 2.0 or up, found 1.2.
|
|
|
|
|
// Fra:340 Mem:7.64M (0.00M, Peak 8.23M) Sce: Scene Ve:0 Fa:0 La:0
|
|
|
|
|
// Saved: /tmp/xx/0_0340.png Time: 00:00.12 (Saving: 00:00.03)
|
|
|
|
|
// Blender quit
|
|
|
|
|
return Type.GPU_NOT_SUPPORTED;
|
|
|
|
|
}
|
|
|
|
|
return Type.OK;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-24 17:52:37 +00:00
|
|
|
protected int getTileSize(Job ajob) {
|
|
|
|
|
int size = 32; // CPU
|
|
|
|
|
GPUDevice gpu = this.config.getGPUDevice();
|
|
|
|
|
if (ajob.getUseGPU() && this.config.getGPUDevice() != null) {
|
|
|
|
|
// GPU
|
|
|
|
|
// if the vram is lower than 1G reduce the size of tile to avoid black output
|
|
|
|
|
size = (gpu.getMemory() > 1073741824L) ? 256 : 128;
|
|
|
|
|
}
|
|
|
|
|
return size;
|
|
|
|
|
}
|
2014-11-20 13:21:19 +00:00
|
|
|
}
|