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 ;
2024-04-14 09:41:06 +00:00
import java.time.Duration ;
import java.time.Instant ;
2014-11-20 13:21:19 +00:00
import java.util.ArrayList ;
import java.util.Calendar ;
import java.util.Date ;
2023-08-07 14:38:40 +00:00
import java.util.List ;
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-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 ;
2024-02-25 18:31:16 +00:00
import java.util.concurrent.Callable ;
import java.util.concurrent.ExecutionException ;
import java.util.concurrent.ExecutorService ;
import java.util.concurrent.Executors ;
import java.util.concurrent.TimeUnit ;
import java.util.concurrent.TimeoutException ;
2023-09-19 17:14:49 +00:00
import java.util.stream.Collectors ;
2014-11-20 13:21:19 +00:00
import com.sheepit.client.Error.ServerCode ;
import com.sheepit.client.Error.Type ;
2023-09-19 17:14:49 +00:00
import com.sheepit.client.datamodel.Chunk ;
2023-01-28 11:45:20 +00:00
import com.sheepit.client.exception.SheepItException ;
import com.sheepit.client.exception.SheepItExceptionNoRendererAvailable ;
import com.sheepit.client.exception.SheepItExceptionNoRightToRender ;
import com.sheepit.client.exception.SheepItExceptionNoSession ;
import com.sheepit.client.exception.SheepItExceptionNoSpaceLeftOnDevice ;
import com.sheepit.client.exception.SheepItExceptionPathInvalid ;
import com.sheepit.client.exception.SheepItExceptionNoWritePermission ;
import com.sheepit.client.exception.SheepItExceptionSessionDisabled ;
import com.sheepit.client.exception.SheepItExceptionSessionDisabledDenoisingNotSupported ;
2023-12-07 15:29:58 +00:00
import com.sheepit.client.exception.SheepItExceptionWithRequiredWait ;
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 ;
2023-12-19 14:35:24 +00:00
private DirectoryManager directoryManager ;
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 ;
2022-09-29 14:07:39 +00:00
private boolean awaitingStop ;
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
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
2024-06-03 14:02:30 +00:00
public Client ( Gui gui , Configuration configuration , String url ) {
2019-08-07 22:17:59 +02:00
this . configuration = configuration ;
2024-06-03 14:02:30 +00:00
this . server = new Server ( url , this . configuration , this ) ;
2019-08-07 22:17:59 +02:00
this . log = Log . getInstance ( this . configuration ) ;
2024-06-03 14:02:30 +00:00
this . gui = gui ;
2023-12-19 14:35:24 +00:00
this . directoryManager = new DirectoryManager ( this . configuration , this . log ) ;
2014-11-20 13:21:19 +00:00
this . renderingJob = null ;
2016-10-31 15:27:20 +01:00
this . previousJob = null ;
2024-06-03 14:02:30 +00:00
this . jobsToValidate = new ArrayBlockingQueue < > ( 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 ;
2022-09-29 14:07:39 +00:00
this . awaitingStop = 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
}
2024-06-03 14:02:30 +00:00
@Override 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 " ) ;
2024-06-03 14:02:30 +00:00
if ( " wait " . equals ( configuration . getShutdownMode ( ) ) ) {
2020-07-28 00:49:36 +10:00
// 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-09-10 10:10:57 +00:00
2023-01-06 14:43:14 +00:00
//send "error" log containing config and fs health check
2022-09-10 10:10:57 +00:00
step = log . newCheckPoint ( ) ;
this . log . info ( " OS: " + OS . getOS ( ) . getVersion ( ) + " " + System . getProperty ( " os.arch " ) ) ;
this . log . info ( configuration . toString ( ) ) ;
2023-01-06 14:43:14 +00:00
for ( String logline : configuration . filesystemHealthCheck ( ) ) {
this . log . info ( logline ) ;
}
2022-09-10 10:10:57 +00:00
sendError ( step , null , Type . OK ) ;
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
2024-06-03 14:02:30 +00:00
Runnable runnableSender = this : : senderLoop ;
Thread threadSender = new Thread ( runnableSender ) ;
threadSender . start ( ) ;
2014-11-20 13:21:19 +00:00
2024-06-08 03:21:33 +00:00
IncompatibleProcessChecker incompatibleProcessChecker = new IncompatibleProcessChecker ( this ) ;
Timer incompatibleProcessCheckerTimer = new Timer ( ) ;
incompatibleProcessCheckerTimer . schedule ( incompatibleProcessChecker , TimeUnit . MINUTES . toMillis ( 1 ) , TimeUnit . MINUTES . toMillis ( 1 ) ) ;
incompatibleProcessChecker . run ( ) ; // before the first request, check if it should be stopped
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 ) {
2024-06-08 03:21:33 +00:00
if ( incompatibleProcessChecker . isSuspendedDueToOtherProcess ( ) ) {
this . gui . status ( " Client paused due to 'incompatible process' feature " , true ) ;
}
else {
this . gui . status ( " Client paused " , true ) ;
this . log . debug ( " Client paused " ) ;
}
2020-04-27 22:28:04 +10:00
}
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 {
2024-06-03 14:02:30 +00:00
Calendar nextRequest = this . nextJobRequest ( ) ;
if ( nextRequest ! = null ) {
2020-04-27 22:28:04 +10:00
// wait
Date now = new Date ( ) ;
2024-06-03 14:02:30 +00:00
this . gui . status ( String . format ( " Waiting until %tR before requesting job " , nextRequest ) ) ;
long wait = nextRequest . getTimeInMillis ( ) - now . getTime ( ) ;
2020-04-27 22:28:04 +10:00
if ( wait < 0 ) {
// it means the client has to wait until the next day
wait + = 24 * 3600 * 1000 ;
}
2023-12-07 15:18:27 +00:00
this . sleep ( wait ) ;
2020-04-27 22:28:04 +10:00
}
2024-06-08 03:21:33 +00:00
if ( incompatibleProcessChecker . isRunningCompatibleProcess ( ) = = false ) {
this . gui . status ( " Requesting Job " ) ;
this . renderingJob = this . server . requestJob ( ) ;
}
else {
this . gui . status ( " Wait until compatible process is stopped " ) ;
this . sleep ( 30 * 1000 ) ;
}
2020-04-26 23:35:05 +10:00
}
2023-01-28 11:45:20 +00:00
catch ( SheepItExceptionNoRightToRender e ) {
2023-01-05 16:25:29 +01:00
this . gui . error ( " User does not have enough right to render project " ) ;
2020-04-27 22:28:04 +10:00
return - 2 ;
2014-12-02 20:28:03 +00:00
}
2023-01-28 11:45:20 +00:00
catch ( SheepItExceptionSessionDisabled e ) {
2020-04-27 22:28:04 +10:00
this . gui . error ( Error . humanString ( Error . Type . SESSION_DISABLED ) ) ;
2023-12-07 15:18:27 +00:00
this . waitForever ( ) ;
2020-04-27 22:28:04 +10:00
}
2023-01-28 11:45:20 +00:00
catch ( SheepItExceptionSessionDisabledDenoisingNotSupported e ) {
2022-07-29 17:13:29 +00:00
this . gui . error ( Error . humanString ( Error . Type . DENOISING_NOT_SUPPORTED ) ) ;
2023-12-07 15:18:27 +00:00
this . waitForever ( ) ;
2022-07-29 17:13:29 +00:00
}
2023-01-28 11:45:20 +00:00
catch ( SheepItExceptionNoRendererAvailable e ) {
2020-04-27 22:28:04 +10:00
this . gui . error ( Error . humanString ( Error . Type . RENDERER_NOT_AVAILABLE ) ) ;
2023-12-07 15:18:27 +00:00
this . waitForever ( ) ;
2020-04-27 22:28:04 +10:00
}
2023-01-28 11:45:20 +00:00
catch ( SheepItExceptionNoSession 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 {
2024-06-03 14:02:30 +00:00
Calendar nextRequest = this . nextJobRequest ( ) ;
if ( nextRequest ! = null ) {
2020-04-27 22:28:04 +10:00
// wait
Date now = new Date ( ) ;
2024-06-03 14:02:30 +00:00
this . gui . status ( String . format ( " Waiting until %tR before requesting job " , nextRequest ) ) ;
long timeToSleep = nextRequest . getTimeInMillis ( ) - now . getTime ( ) ;
2023-12-07 15:18:27 +00:00
this . activeSleep ( timeToSleep ) ;
2020-04-27 22:28:04 +10:00
}
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 ( ) ;
}
2023-01-28 11:45:20 +00:00
catch ( SheepItException e1 ) {
2020-04-27 22:28:04 +10:00
this . renderingJob = null ;
}
2014-11-20 13:21:19 +00:00
}
}
2023-12-07 15:29:58 +00:00
catch ( SheepItExceptionWithRequiredWait e ) {
// exception example:
// SheepItExceptionServerInMaintenance
// SheepItExceptionServerOverloaded
// SheepItServerDown
// SheepItExceptionBadResponseFromServer
2023-12-07 15:18:27 +00:00
2024-06-03 14:02:30 +00:00
int timeSleep = e . getWaitDuration ( ) ;
this . gui . status ( String . format ( e . getHumanText ( ) , new Date ( new Date ( ) . getTime ( ) + timeSleep ) ) ) ;
2023-12-07 15:18:27 +00:00
2024-06-03 14:02:30 +00:00
if ( this . activeSleep ( timeSleep ) = = false ) {
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
}
2023-01-28 11:45:20 +00:00
catch ( SheepItException e ) {
2020-04-27 22:28:04 +10:00
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
2024-06-03 14:02:30 +00:00
int [ ] retrySchemeInMilliSeconds = { 300_000 , 480_000 , 720_000 , 900_000 , 1_200_000 } ; // 5, 8, 12, 15 and 20 minutes
2020-05-20 22:32:39 +10:00
2024-06-03 14:02:30 +00:00
int timeSleep = 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 ) ] ;
2024-06-03 14:02:30 +00:00
this . gui . status ( String . format ( " No job available. Will try again at %tR " , new Date ( new Date ( ) . getTime ( ) + timeSleep ) ) ) ;
if ( this . activeSleep ( timeSleep ) = = false ) {
2023-12-07 15:18:27 +00:00
return - 3 ;
2020-04-27 22:28:04 +10: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
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 ) {
2024-06-03 14:02:30 +00:00
Job frameToReset = 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 ) ) ;
2024-06-03 14:02:30 +00:00
this . sendError ( step , frameToReset , ret ) ;
2020-04-27 22:28:04 +10:00
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. " ) ;
2023-12-07 15:18:27 +00:00
this . waitForever ( ) ;
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
}
2023-09-09 13:17:15 +02:00
this . renderingJob = null ;
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 ( ) ) {
2023-12-07 15:18:27 +00:00
this . sleep ( 4000 ) ;
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
2023-12-07 15:18:27 +00:00
this . gui . status ( " Uploading rendered frames before exiting. Please wait " ) ;
this . sleep ( 2300 ) ;
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 ) ;
2023-07-15 14:49:03 +00:00
this . renderingJob . getProcessRender ( ) . kill ( ) ;
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 " ) ) ;
}
2024-06-03 14:02:30 +00:00
catch ( IOException ignored ) {
2020-01-12 14:37:30 +01:00
// 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 ( ) ;
}
2023-12-07 15:18:27 +00:00
public void waitForever ( ) {
// the client is most likely dead.
// instead of exiting, wait forever to display an error message on the UI
while ( shuttingdown = = false ) {
try {
Thread . sleep ( 1000 ) ;
}
catch ( InterruptedException ignored ) {
}
}
}
/ * *
* Sleep but stop if the client is not running or shutting down
* @param wait in ms
* /
public boolean activeSleep ( long wait ) {
try {
long timeSlept = 0 ;
while ( timeSlept < wait & & this . running & & this . shuttingdown = = false ) {
Thread . sleep ( 1000 ) ;
timeSlept + = 1000 ;
}
return true ;
}
catch ( InterruptedException ignored ) {
return false ;
}
}
/ * *
* @param wait in ms
* /
public boolean sleep ( long wait ) {
try {
int timeSlept = 0 ;
while ( timeSlept < wait ) {
Thread . sleep ( 1000 ) ;
timeSlept + = 1000 ;
}
return true ;
}
catch ( InterruptedException ignored ) {
return false ;
}
}
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 ;
2022-09-29 14:07:39 +00:00
this . awaitingStop = true ;
2014-11-20 13:21:19 +00:00
}
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 ;
2022-09-29 14:07:39 +00:00
this . awaitingStop = false ;
2015-08-05 19:58:11 +01:00
}
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
}
}
2024-06-03 14:02:30 +00:00
protected void sendError ( int step ) {
this . sendError ( step , null , null ) ;
2014-11-20 13:21:19 +00:00
}
2024-06-03 14:02:30 +00:00
protected void sendError ( int step , Job jobToReset , Error . Type error ) {
2014-11-20 13:21:19 +00:00
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 {
2024-06-03 14:02:30 +00:00
File tempFile = File . createTempFile ( " farm_ " , " .txt " ) ;
tempFile . createNewFile ( ) ;
tempFile . deleteOnExit ( ) ;
FileOutputStream writer = new FileOutputStream ( tempFile ) ;
2014-11-20 13:21:19 +00:00
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 ) )
2023-10-16 18:05:29 +00:00
. append ( String . format ( " %s x%d %.1f GB RAM \ n " , cpu . getName ( ) , 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 " ) ;
2024-06-03 14:02:30 +00:00
if ( jobToReset ! = null ) {
logHeader . append ( String . format ( " Project ::: %s \ n " , jobToReset . getName ( ) ) )
. append ( String . format ( " Project id: %s frame: %s \ n " , jobToReset . getId ( ) , jobToReset . getFrameNumber ( ) ) ) . append ( String . format ( " blender ::: %s \ n \ n " , jobToReset . 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 ( ) ) ;
2024-06-03 14:02:30 +00:00
Optional < ArrayList < String > > logs = this . log . getForCheckPoint ( step ) ;
2020-10-27 14:32:11 +01:00
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 ( ) ) ) ;
2024-06-03 14:02:30 +00:00
if ( jobToReset ! = null ) {
remoteURL . addQueryParameter ( " frame " , jobToReset . getFrameNumber ( ) ) ;
remoteURL . addQueryParameter ( " job " , jobToReset . getId ( ) ) ;
remoteURL . addQueryParameter ( " render_time " , Integer . toString ( jobToReset . getProcessRender ( ) . getRenderDuration ( ) ) ) ;
remoteURL . addQueryParameter ( " memoryused " , Long . toString ( jobToReset . getProcessRender ( ) . getPeakMemoryUsed ( ) ) ) ;
2014-11-20 13:21:19 +00:00
}
2024-06-03 14:02:30 +00:00
this . server . HTTPSendFile ( remoteURL . build ( ) . toString ( ) , tempFile . getAbsolutePath ( ) , step , this . gui ) ;
tempFile . delete ( ) ;
2014-11-20 13:21:19 +00:00
}
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
}
}
/ * *
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 ;
2023-08-07 14:38:40 +00:00
List < Calendar > dates = new ArrayList < > ( ) ;
2014-11-20 13:21:19 +00:00
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 ;
}
2023-08-07 14:38:40 +00:00
Calendar startTomorow = ( Calendar ) start . clone ( ) ;
startTomorow . add ( Calendar . DATE , 1 ) ;
dates . add ( start ) ;
dates . add ( startTomorow ) ;
}
for ( Calendar cal : dates ) {
if ( cal . after ( now ) & & ( next = = null | | ( cal . getTimeInMillis ( ) - now . getTimeInMillis ( ) < next . getTimeInMillis ( ) - now . getTimeInMillis ( ) ) ) ) {
next = cal ;
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 ( " " ) ;
2023-01-06 14:43:14 +00:00
for ( String logline : configuration . filesystemHealthCheck ( ) ) {
log . debug ( logline ) ;
}
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 ( " " ) ;
2023-01-06 14:43:14 +00:00
for ( String logline : configuration . filesystemHealthCheck ( ) ) {
log . debug ( logline ) ;
}
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 ( " " ) ;
2023-01-06 14:43:14 +00:00
for ( String logline : configuration . filesystemHealthCheck ( ) ) {
log . debug ( logline ) ;
}
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
}
2023-01-28 11:45:20 +00:00
catch ( SheepItException e ) {
2016-10-12 00:34:51 +02:00
gui . setRenderingProjectName ( " " ) ;
2023-01-06 14:43:14 +00:00
for ( String logline : configuration . filesystemHealthCheck ( ) ) {
log . debug ( logline ) ;
}
2023-01-28 11:45:20 +00:00
if ( e instanceof SheepItExceptionNoSpaceLeftOnDevice ) {
2022-03-11 14:02:22 +00:00
return Error . Type . NO_SPACE_LEFT_ON_DEVICE ;
}
2023-01-28 11:45:20 +00:00
else if ( e instanceof SheepItExceptionPathInvalid ) {
2022-03-11 14:02:22 +00:00
return Error . Type . PATH_INVALID ;
}
2023-01-28 11:45:20 +00:00
else if ( e instanceof SheepItExceptionNoWritePermission ) {
2022-03-11 14:02:22 +00:00
return Error . Type . NO_WRITE_PERMISSION ;
}
else {
return Error . Type . UNKNOWN ;
}
2014-11-20 13:21:19 +00:00
}
2024-06-03 14:02:30 +00:00
final File sceneFile = new File ( ajob . getScenePath ( ) ) ;
File rendererFile = new File ( ajob . getRendererPath ( ) ) ;
2014-11-20 13:21:19 +00:00
2024-06-03 14:02:30 +00:00
if ( sceneFile . exists ( ) = = false ) {
2016-10-12 00:34:51 +02:00
gui . setRenderingProjectName ( " " ) ;
2023-01-06 14:43:14 +00:00
for ( String logline : configuration . filesystemHealthCheck ( ) ) {
log . debug ( logline ) ;
}
2024-06-03 14:02:30 +00:00
this . log . error ( " Client::work job preparation failed (scene file ' " + sceneFile . getAbsolutePath ( )
2020-05-28 13:28:42 +02:00
+ " ' 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 ;
}
2024-06-03 14:02:30 +00:00
if ( rendererFile . exists ( ) = = false ) {
2016-10-12 00:34:51 +02:00
gui . setRenderingProjectName ( " " ) ;
2023-01-06 14:43:14 +00:00
for ( String logline : configuration . filesystemHealthCheck ( ) ) {
log . debug ( logline ) ;
}
2024-06-03 14:02:30 +00:00
this . log . error ( " Client::work job preparation failed (renderer file ' " + rendererFile . getAbsolutePath ( )
2020-05-28 13:28:42 +02:00
+ " ' 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
2024-06-03 14:02:30 +00:00
sceneFile . 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 ( " " ) ;
2023-09-26 13:38:42 +02:00
removeSceneDirectory ( ajob ) ;
2014-11-20 13:21:19 +00:00
if ( err ! = Error . Type . OK ) {
this . log . error ( " Client::work problem with runRenderer (ret " + err + " ) " ) ;
}
2024-04-19 18:40:55 +00:00
return err ;
2014-11-20 13:21:19 +00:00
}
2024-06-03 14:02:30 +00:00
protected Error . Type downloadSceneFile ( Job ajob ) throws SheepItException {
int total = ajob . getArchiveChunks ( ) . size ( ) ;
2024-04-30 16:56:29 +00:00
int threads = Math . max ( 1 , Math . min ( total , 12 ) ) ; // at least one thread, to avoid IllegalArgumentException if total = 0
2024-02-25 18:31:16 +00:00
2024-04-30 16:56:29 +00:00
ExecutorService executor = Executors . newFixedThreadPool ( threads ) ;
2024-02-25 18:31:16 +00:00
ArrayList < Callable < Error . Type > > tasks = new ArrayList < > ( ) ;
2024-04-11 15:24:50 +00:00
this . gui . getDownloadProgress ( ) . reset ( " Downloading project " ) ;
2023-09-21 07:52:11 +00:00
for ( int i = 0 ; i < total ; i + + ) {
2024-06-03 14:02:30 +00:00
Chunk chunk = ajob . getArchiveChunks ( ) . get ( i ) ;
2024-02-25 18:31:16 +00:00
Callable < Type > downloadTask = ( ) - > {
DownloadManager downloadManager = new DownloadManager (
this . server ,
this . gui ,
this . log ,
this . directoryManager . getActualStoragePathFor ( chunk ) ,
chunk . getMd5 ( ) ,
String . format ( LOCALE , " %s?chunk=%s " , this . server . getPage ( " download-chunk " ) , chunk . getId ( ) )
) ;
2024-06-03 14:02:30 +00:00
return downloadManager . download ( ) ;
2024-02-25 18:31:16 +00:00
} ;
tasks . add ( downloadTask ) ;
}
try {
var results = executor . invokeAll ( tasks ) ;
for ( var result : results ) {
if ( result . get ( 35 , TimeUnit . MINUTES ) ! = Type . OK ) {
executor . shutdown ( ) ;
return result . get ( 35 , TimeUnit . MINUTES ) ;
}
2023-09-19 17:14:49 +00:00
}
}
2024-02-25 18:31:16 +00:00
catch ( ExecutionException | InterruptedException | TimeoutException e ) {
executor . shutdown ( ) ;
return Type . DOWNLOAD_FILE ;
}
executor . shutdown ( ) ;
2023-09-19 17:14:49 +00:00
return Type . OK ;
2014-11-20 13:21:19 +00:00
}
2023-01-28 11:45:20 +00:00
protected Error . Type downloadExecutable ( Job ajob ) throws SheepItException {
2024-04-11 15:24:50 +00:00
this . gui . getDownloadProgress ( ) . reset ( " Downloading Blender " ) ;
2023-12-07 12:27:53 +00:00
return ( new DownloadManager (
this . server ,
this . gui ,
this . log ,
2023-12-19 14:35:24 +00:00
this . directoryManager . getActualStorageBinaryPathFor ( ajob ) ,
2023-12-07 12:27:53 +00:00
ajob . getRendererMD5 ( ) ,
String . format ( LOCALE , " %s?job=%s " , this . server . getPage ( " download-binary " ) , ajob . getId ( ) )
) ) . download ( ) ;
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 ;
2023-12-19 14:35:24 +00:00
2024-06-03 14:02:30 +00:00
String rendererArchive = this . directoryManager . getCacheBinaryPathFor ( ajob ) ;
String rendererPath = ajob . getRendererDirectory ( ) ;
File rendererPathFile = new File ( rendererPath ) ;
2014-11-20 13:21:19 +00:00
2023-12-19 14:35:24 +00:00
// file is already downloaded, either on shared directory or cache directory (from this.downloadExecutable)
if ( this . directoryManager . isSharedEnabled ( ) & & new File ( this . directoryManager . getSharedBinaryPathFor ( ajob ) ) . exists ( ) ) {
2020-10-21 22:03:09 +11:00
this . gui . status ( " Copying renderer from shared downloads directory " ) ;
2023-12-19 14:35:24 +00:00
this . log . debug ( " Client::prepareWorkingDirectory Copying renderer from shared downloads directory " + this . directoryManager . getSharedBinaryPathFor ( ajob ) + " into " + this . directoryManager . getCacheBinaryPathFor ( ajob ) ) ;
if ( this . directoryManager . copyBinaryFromSharedToCache ( ajob ) = = false ) {
2024-06-03 14:02:30 +00:00
log . error ( " Error while copying " + rendererArchive + " from shared downloads directory to working dir " ) ;
2023-12-19 14:35:24 +00:00
}
2014-11-20 13:21:19 +00:00
}
2020-10-21 22:03:09 +11:00
2024-06-03 14:02:30 +00:00
if ( ! rendererPathFile . exists ( ) ) {
2014-11-20 13:21:19 +00:00
// we create the directory
2024-06-03 14:02:30 +00:00
rendererPathFile . mkdir ( ) ;
2014-11-20 13:21:19 +00:00
2020-10-21 22:03:09 +11:00
this . gui . status ( " Extracting renderer " ) ;
2024-06-03 14:02:30 +00:00
this . log . debug ( " Client::prepareWorkingDirectory Extracting renderer " + rendererArchive + " into " + rendererPath ) ;
2020-10-21 22:03:09 +11:00
2014-11-20 13:21:19 +00:00
// unzip the archive
2024-06-03 14:02:30 +00:00
ret = Utils . unzipFileIntoDirectory ( rendererArchive , rendererPath , null , log ) ;
2014-11-20 13:21:19 +00:00
if ( ret ! = 0 ) {
2020-05-28 13:28:42 +02:00
this . log . error (
2024-06-03 14:02:30 +00:00
" Client::prepareWorkingDirectory, error(1) with Utils.unzipFileIntoDirectory( " + rendererArchive + " , " + rendererPath + " ) returned "
2020-05-28 13:28:42 +02:00
+ 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
}
2024-06-03 14:02:30 +00:00
String scenePath = ajob . getSceneDirectory ( ) ;
File scenePathFile = new File ( scenePath ) ;
2014-11-20 13:21:19 +00:00
2023-12-19 14:35:24 +00:00
// chunk files are already downloaded, either on shared directory or cache directory (from this.downloadSceneFile)
2023-09-19 17:14:49 +00:00
for ( Chunk chunk : ajob . getArchiveChunks ( ) ) {
2023-12-19 14:35:24 +00:00
if ( this . directoryManager . isSharedEnabled ( ) & & new File ( this . directoryManager . getSharedPathFor ( chunk ) ) . exists ( ) ) {
2023-09-19 17:14:49 +00:00
this . gui . status ( " Copying chunk from common directory " ) ;
2023-12-19 14:35:24 +00:00
if ( this . directoryManager . copyChunkFromSharedToCache ( chunk ) = = false ) {
this . log . error ( " Error while copying " + this . directoryManager . getSharedPathFor ( chunk ) + " from shared downloads directory to working dir " ) ;
}
2023-09-19 17:14:49 +00:00
}
2023-12-19 14:35:24 +00:00
2014-11-20 13:21:19 +00:00
}
2020-10-21 22:03:09 +11:00
2023-09-19 17:14:49 +00:00
/// download the chunks
2024-06-03 14:02:30 +00:00
if ( ! scenePathFile . exists ( ) ) {
2014-11-20 13:21:19 +00:00
// we create the directory
2024-06-03 14:02:30 +00:00
scenePathFile . mkdir ( ) ;
2014-11-20 13:21:19 +00:00
2020-10-21 22:03:09 +11:00
this . gui . status ( " Extracting project " ) ;
2024-06-03 14:02:30 +00:00
this . log . debug ( " Client::prepareWorkingDirectory Extracting project into " + scenePath ) ;
2014-11-20 13:21:19 +00:00
// unzip the archive
2023-09-19 17:14:49 +00:00
2024-04-14 09:41:06 +00:00
Instant startUnzip = Instant . now ( ) ;
2023-09-19 17:14:49 +00:00
ret = Utils . unzipChunksIntoDirectory (
2023-12-19 14:35:24 +00:00
ajob . getArchiveChunks ( ) . stream ( ) . map ( chunk - > this . directoryManager . getCachePathFor ( chunk ) ) . collect ( Collectors . toList ( ) ) ,
2024-06-03 14:02:30 +00:00
scenePath ,
2023-09-19 17:14:49 +00:00
ajob . getPassword ( ) ,
log ) ;
2024-04-14 09:41:06 +00:00
Instant stopUnzip = Instant . now ( ) ;
Duration unzipDuration = Duration . between ( startUnzip , stopUnzip ) ;
log . debug ( " Unzipping " + ajob . getArchiveChunks ( ) . size ( ) + " chunks of \" " + ajob . getName ( ) + " \" took " + unzipDuration . toSeconds ( ) + " s " ) ;
2014-11-20 13:21:19 +00:00
if ( ret ! = 0 ) {
2023-09-19 17:14:49 +00:00
this . log . error ( " Client::prepareWorkingDirectory, error(2) with Utils.unzipChunksIntoDirectory 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
2020-06-18 21:57:52 +10:00
protected Error . Type confirmJob ( Job ajob , int checkpoint ) {
2024-06-03 14:02:30 +00:00
String urlReal = String . format ( LOCALE , " %s&rendertime=%d&preptime=%d&memoryused=%s " , ajob . getValidationUrl ( ) , ajob . getProcessRender ( ) . getRenderDuration ( ) , ajob . getProcessRender ( ) . getScenePrepDuration ( ) ,
2021-11-16 14:51:53 +00:00
ajob . getProcessRender ( ) . getPeakMemoryUsed ( ) ) ;
2023-02-02 18:25:53 +01:00
if ( ajob . getSpeedSamplesRendered ( ) > 0 . 0 ) {
2024-06-03 14:02:30 +00:00
urlReal + = String . format ( LOCALE , " &speedsamples=%s " , ajob . getSpeedSamplesRendered ( ) ) ;
2023-02-02 18:25:53 +01:00
}
2024-06-03 14:02:30 +00:00
this . log . debug ( checkpoint , " Client::confirmeJob url " + urlReal ) ;
2020-06-18 21:57:52 +10:00
this . log . debug ( checkpoint , " path frame " + ajob . getOutputImagePath ( ) ) ;
2014-11-20 13:21:19 +00:00
this . isValidatingJob = true ;
2024-06-03 14:02:30 +00:00
int maxTries = 3 ;
int timeToSleep = 22_000 ;
ServerCode ret ;
2020-05-10 06:04:07 +10:00
Type confirmJobReturnCode = Error . Type . OK ;
2020-05-28 13:28:42 +02:00
retryLoop :
2024-06-03 14:02:30 +00:00
for ( int nbTry = 0 ; nbTry < maxTries ; nbTry + + ) {
if ( nbTry > = 1 ) {
2022-03-12 12:43:10 +01:00
// sleep before retrying
2023-01-06 16:04:20 +01:00
this . log . debug ( checkpoint , " Sleep for " + timeToSleep / 1000 + " s before trying to re-upload the frame, previous error: " + confirmJobReturnCode ) ;
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
}
2024-06-03 14:02:30 +00:00
ret = this . server . HTTPSendFile ( urlReal , 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 ;
2022-08-30 18:11:17 +00:00
case SERVER_CONNECTION_FAILED :
confirmJobReturnCode = Type . NETWORK_ISSUE ;
break ;
2022-07-17 11:40:01 +02:00
case ERROR_BAD_RESPONSE :
// set the error and retry on next loop
confirmJobReturnCode = Type . ERROR_BAD_UPLOAD_RESPONSE ;
break ;
2022-08-30 18:11:17 +00:00
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
2024-02-28 11:31:23 +00:00
Utils . deleteFile ( new File ( ajob . getOutputImagePath ( ) ) ) ;
2014-11-20 13:21:19 +00:00
ajob . setOutputImagePath ( null ) ;
2020-05-28 13:28:42 +02:00
2023-01-06 14:53:06 +00:00
if ( ajob . getPreviewImagePath ( ) ! = null ) {
2024-02-28 11:31:23 +00:00
Utils . deleteFile ( new File ( ajob . getPreviewImagePath ( ) ) ) ;
2023-01-06 14:53:06 +00:00
ajob . setPreviewImagePath ( null ) ;
}
2020-05-10 06:04:07 +10:00
return confirmJobReturnCode ;
2014-11-20 13:21:19 +00:00
}
protected boolean shouldWaitBeforeRender ( ) {
2024-06-03 14:02:30 +00:00
int concurrentJob = this . jobsToValidate . size ( ) ;
2014-11-20 13:21:19 +00:00
if ( this . isValidatingJob ) {
2024-06-03 14:02:30 +00:00
concurrentJob + + ;
2014-11-20 13:21:19 +00:00
}
2024-06-03 14:02:30 +00:00
return ( concurrentJob > = 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
* /
2024-06-03 14:02:30 +00:00
@AllArgsConstructor private class QueuedJob {
2020-06-18 21:57:52 +10:00
final private int checkpoint ;
final private Job job ;
}
2014-11-20 13:21:19 +00:00
}