Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force stop Java Files.copy() running on external thread

Tags:

The answer here seemed to be a valid solution before Java 8: How to cancel Files.copy() in Java?

But now it doesn't work, because ExtendedCopyOption.INTERRUPTIBLE is private.


Basically, I need to download a file from some given URL and save it to my local file-system using Files.copy(). Currently, I am using a JavaFX Service because I need to show the progress in a ProgressBar.

However, I don't know how to block the thread running Files.copy() if the operation takes too long. Using Thread.stop() is at least not wanted. Even Thread.interrupt() fails.

I also want the operation to terminate gracefully if the internet connection becomes unavailable.

To test the case when no internet connection is available, I'm removing my ethernet cable and putting it back after 3 seconds. Unfortunately, Files.copy() returns only when I put back the ethernet cable, while I would like it to fail immediately.

As I can see, internally Files.copy() is running a loop, which prevents the thread from exiting.


Tester(Downloading OBS Studio exe):

/**  * @author GOXR3PLUS  *  */ public class TestDownloader extends Application {      /**      * @param args      */     public static void main(String[] args) {     launch(args);     }      @Override     public void start(Stage primaryStage) throws Exception {     // Block From exiting     Platform.setImplicitExit(false);      // Try to download the File from URL     new DownloadService().startDownload(         "https://github.com/jp9000/obs-studio/releases/download/17.0.2/OBS-Studio-17.0.2-Small-Installer.exe",         System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "OBS-Studio-17.0.2-Small-Installer.exe");      }  } 

DownloadService:

Using @sillyfly comment with FileChannel and removing File.copy seems to work only with calling Thread.interrupt() but it is not exiting when the internet is not available..

import java.io.File; import java.net.URL; import java.net.URLConnection; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.file.StandardOpenOption; import java.util.logging.Level; import java.util.logging.Logger;  import javafx.concurrent.Service; import javafx.concurrent.Task;  /**  * JavaFX Service which is Capable of Downloading Files from the Internet to the  * LocalHost  *   * @author GOXR3PLUS  *  */ public class DownloadService extends Service<Boolean> {      // -----     private long totalBytes;     private boolean succeeded = false;     private volatile boolean stopThread;      // CopyThread     private Thread copyThread = null;      // ----     private String urlString;     private String destination;      /**      * The logger of the class      */     private static final Logger LOGGER = Logger.getLogger(DownloadService.class.getName());      /**      * Constructor      */     public DownloadService() {     setOnFailed(f -> System.out.println("Failed with value: " + super.getValue()+" , Copy Thread is Alive? "+copyThread.isAlive()));     setOnSucceeded(s -> System.out.println("Succeeded with value: " + super.getValue()+" , Copy Thread is Alive? "+copyThread.isAlive()));     setOnCancelled(c -> System.out.println("Succeeded with value: " + super.getValue()+" , Copy Thread is Alive? "+copyThread.isAlive()));     }      /**      * Start the Download Service      *       * @param urlString      *            The source File URL      * @param destination      *            The destination File      */     public void startDownload(String urlString, String destination) {     if (!super.isRunning()) {         this.urlString = urlString;         this.destination = destination;         totalBytes = 0;         restart();     }     }      @Override     protected Task<Boolean> createTask() {     return new Task<Boolean>() {         @Override         protected Boolean call() throws Exception {          // Succeeded boolean         succeeded = true;          // URL and LocalFile         URL urlFile = new URL(java.net.URLDecoder.decode(urlString, "UTF-8"));         File destinationFile = new File(destination);          try {             // Open the connection and get totalBytes             URLConnection connection = urlFile.openConnection();             totalBytes = Long.parseLong(connection.getHeaderField("Content-Length"));                  // --------------------- Copy the File to External Thread-----------             copyThread = new Thread(() -> {              // Start File Copy             try (FileChannel zip = FileChannel.open(destinationFile.toPath(), StandardOpenOption.CREATE,                 StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {                  zip.transferFrom(Channels.newChannel(connection.getInputStream()), 0, Long.MAX_VALUE);                   // Files.copy(dl.openStream(), fl.toPath(),StandardCopyOption.REPLACE_EXISTING)              } catch (Exception ex) {                 stopThread = true;                 LOGGER.log(Level.WARNING, "DownloadService failed", ex);             }              System.out.println("Copy Thread exited...");             });             // Set to Daemon             copyThread.setDaemon(true);             // Start the Thread             copyThread.start();             // -------------------- End of Copy the File to External Thread-------                   // ---------------------------Check the %100 Progress--------------------             long outPutFileLength;             long previousLength = 0;             int failCounter = 0;             // While Loop             while ((outPutFileLength = destinationFile.length()) < totalBytes && !stopThread) {              // Check the previous length             if (previousLength != outPutFileLength) {                 previousLength = outPutFileLength;                 failCounter = 0;             } else                 ++failCounter;              // 2 Seconds passed without response             if (failCounter == 40 || stopThread)                 break;              // Update Progress             super.updateProgress((outPutFileLength * 100) / totalBytes, 100);             System.out.println("Current Bytes:" + outPutFileLength + " ,|, TotalBytes:" + totalBytes                 + " ,|, Current Progress: " + (outPutFileLength * 100) / totalBytes + " %");              // Sleep             try {                 Thread.sleep(50);             } catch (InterruptedException ex) {                 LOGGER.log(Level.WARNING, "", ex);             }             }              // 2 Seconds passed without response             if (failCounter == 40)             succeeded = false;            // --------------------------End of Check the %100 Progress--------------------          } catch (Exception ex) {             succeeded = false;             // Stop the External Thread which is updating the %100             // progress             stopThread = true;             LOGGER.log(Level.WARNING, "DownloadService failed", ex);         }                //----------------------Finally------------------------------          System.out.println("Trying to interrupt[shoot with an assault rifle] the copy Thread");          // ---FORCE STOP COPY FILES         if (copyThread != null && copyThread.isAlive()) {             copyThread.interrupt();             System.out.println("Done an interrupt to the copy Thread");              // Run a Looping checking if the copyThread has stopped...             while (copyThread.isAlive()) {             System.out.println("Copy Thread is still Alive,refusing to die.");             Thread.sleep(50);             }         }          System.out.println("Download Service exited:[Value=" + succeeded + "] Copy Thread is Alive? "             + (copyThread == null ? "" : copyThread.isAlive()));          //---------------------- End of Finally------------------------------             return succeeded;         }      };     }  } 

Interesting questions:

1-> What does java.lang.Thread.interrupt() do?

like image 588
GOXR3PLUS Avatar asked Feb 05 '17 10:02

GOXR3PLUS


People also ask

How to force stop a thread in Java?

Previously, methods suspend(), resume() and stop() were used to manage the execution of threads. But these methods were deprecated by Java 2 because they could result in system failures. Modern ways to suspend/stop a thread are by using a boolean flag and Thread. interrupt() method.

How to copy file from source to destination in Java?

1) The Files. copy() should be a standard way to copy a file from one folder to another if you are working in Java 7 and Java 8. 2) For copying files in Java 6, you can either write your own code using FileChannel, FileInputStream or can leverage Apache Commons IO.

How to duplicate a file using Java?

If you are working on Java 7 or higher, you can use Files class copy() method to copy file in java. It uses File System providers to copy the files.

How do you delete a thread in Java?

Whenever we want to stop a thread from running state by calling stop() method of Thread class in Java. This method stops the execution of a running thread and removes it from the waiting threads pool and garbage collected.


1 Answers

I strongly encourage you to use a FileChannel. It has the transferFrom() method which returns immediately when the thread running it is interrupted. (The Javadoc here says that it should raise a ClosedByInterruptException, but it doesn't.)

try (FileChannel channel = FileChannel.open(Paths.get(...), StandardOpenOption.CREATE,                                             StandardOpenOption.WRITE)) {     channel.transferFrom(Channels.newChannel(new URL(...).openStream()), 0, Long.MAX_VALUE); } 

It also has the potential to perform much better than its java.io alternative. (However, it turns out that the implementation of Files.copy() may elect to delegate to this method instead of actually performing the copy by itself.)


Here's an example of a reusable JavaFX Service that lets you fetch a resource from the internet and save it to your local file-system, with automatic graceful termination if the operation takes too long.

  • The service task (spawned by createTask()) is the user of the file-channel API.
  • A separate ScheduledExecutorService is used to handle the time constraint.
  • Always stick to the good practices for extending Service.
  • If you choose to use such an high-level method, you won't be able to track down the progress of the task.
  • If the connection becomes unavailable, transferFrom() should eventually return without throwing an exception.

To start the service (may be done from any thread):

DownloadService downloadService = new DownloadService(); downloadService.setRemoteResourceLocation(new URL("http://speedtest.ftp.otenet.gr/files/test1Gb.db")); downloadService.setPathToLocalResource(Paths.get("C:", "test1Gb.db")); downloadService.start(); 

and then to cancel it (otherwise it will be automatically cancelled after the time expires):

downloadService.cancel(); 

Note that the same service can be reused, just be sure to reset it before starting again:

downloadService.reset(); 

Here is the DownloadService class:

public class DownloadService extends Service<Void> {      private static final long TIME_BUDGET = 2; // In seconds      private final ScheduledExecutorService watchdogService =             Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {                 private final ThreadFactory delegate = Executors.defaultThreadFactory();                  @Override                 public Thread newThread(Runnable r) {                     Thread thread = delegate.newThread(r);                     thread.setDaemon(true);                     return thread;                 }             });     private Future<?> watchdogThread;      private final ObjectProperty<URL> remoteResourceLocation = new SimpleObjectProperty<>();     private final ObjectProperty<Path> pathToLocalResource = new SimpleObjectProperty<>();      public final URL getRemoteResourceLocation() {         return remoteResourceLocation.get();     }      public final void setRemoteResourceLocation(URL remoteResourceLocation) {         this.remoteResourceLocation.set(remoteResourceLocation);     }      public ObjectProperty<URL> remoteResourceLocationProperty() {         return remoteResourceLocation;     }      public final Path getPathToLocalResource() {         return pathToLocalResource.get();     }      public final void setPathToLocalResource(Path pathToLocalResource) {         this.pathToLocalResource.set(pathToLocalResource);     }      public ObjectProperty<Path> pathToLocalResourceProperty() {         return pathToLocalResource;     }      @Override     protected Task<Void> createTask() {         final Path pathToLocalResource = getPathToLocalResource();         final URL remoteResourceLocation = getRemoteResourceLocation();         if (pathToLocalResource == null) {             throw new IllegalStateException("pathToLocalResource property value is null");         }         if (remoteResourceLocation == null) {             throw new IllegalStateException("remoteResourceLocation property value is null");         }          return new Task<Void>() {             @Override             protected Void call() throws IOException {                 try (FileChannel channel = FileChannel.open(pathToLocalResource, StandardOpenOption.CREATE,                                                             StandardOpenOption.WRITE)) {                     channel.transferFrom(Channels.newChannel(remoteResourceLocation.openStream()), 0, Long.MAX_VALUE);                 }                 return null;             }         };     }      @Override     protected void running() {         watchdogThread = watchdogService.schedule(() -> {             Platform.runLater(() -> cancel());         }, TIME_BUDGET, TimeUnit.SECONDS);     }      @Override     protected void succeeded() {         watchdogThread.cancel(false);     }      @Override     protected void cancelled() {         watchdogThread.cancel(false);     }      @Override     protected void failed() {         watchdogThread.cancel(false);     }  } 
like image 83
Atom 12 Avatar answered Oct 16 '22 10:10

Atom 12