- Timestamp:
- 2017-04-05T20:22:15+12:00 (7 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
main/trunk/greenstone3/src/java/org/greenstone/gsdl3/build/GS2PerlConstructor.java
r30617 r31574 5 5 import org.greenstone.util.Misc; 6 6 import org.greenstone.util.GlobalProperties; 7 import org.greenstone.util.SafeProcess; 7 8 8 9 // xml classes … … 27 28 * perl scripts to do the building stuff 28 29 */ 29 public class GS2PerlConstructor extends CollectionConstructor 30 public class GS2PerlConstructor extends CollectionConstructor implements SafeProcess.ExceptionHandler 30 31 { 31 32 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.build.GS2PerlConstructor.class.getName()); … … 374 375 return runPerlCommand(command, null, null); 375 376 } 376 377 protected boolean runPerlCommand(String[] command, String[] envvars, File dir) 377 378 379 protected boolean runPerlCommand(String[] command, String[] envvars, File dir) 380 { 381 boolean success = true; 382 383 int sepIndex = this.gsdl3home.lastIndexOf(File.separator); 384 String srcHome = this.gsdl3home.substring(0, sepIndex); 385 386 ArrayList<String> args = new ArrayList<String>(); 387 args.add("GSDLHOME=" + this.gsdl2home); 388 args.add("GSDL3HOME=" + this.gsdl3home); 389 args.add("GSDL3SRCHOME=" + srcHome); 390 args.add("GSDLOS=" + this.gsdlos); 391 args.add("GSDL-RUN-SETUP=true"); 392 args.add("PERL_PERTURB_KEYS=0"); 393 394 if(envvars != null) { 395 for(int i = 0; i < envvars.length; i++) { 396 args.add(envvars[i]); 397 } 398 } 399 400 for (String a : System.getenv().keySet()) { 401 args.add(a + "=" + System.getenv(a)); 402 } 403 404 String command_str = ""; 405 for (int i = 0; i < command.length; i++) { 406 command_str = command_str + command[i] + " "; 407 } 408 409 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str)); 410 411 412 logger.info("### Running command = " + command_str); 413 414 // This is where we create and run our perl process safely 415 SafeProcess perlProcess 416 = new SafeProcess(command, args.toArray(new String[args.size()]), dir); // dir can be null 417 418 419 sendProcessBegun(new ConstructionEvent(this, GSStatus.ACCEPTED, "starting")); 420 421 File logDir = new File(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator + "log"); 422 if (!logDir.exists()) { 423 logDir.mkdir(); 424 } 425 426 // Only from Java 7+: Try-with-Resources block will safely close the BufferedWriter 427 // https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html 428 BufferedWriter bw = null; 429 try { 430 bw = new BufferedWriter(new FileWriter(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator + "log" + File.separator + "build_log." + (System.currentTimeMillis()) + ".txt")); 431 432 bw.write("Document Editor Build \n"); 433 bw.write("Command = " + command_str + "\n"); 434 435 // handle each incoming line from stdout and stderr streams, and any exceptions that occur then 436 SynchronizedProcessLineByLineHandler outLineByLineHandler 437 = new SynchronizedProcessLineByLineHandler(bw, SynchronizedProcessLineByLineHandler.STDOUT); 438 SynchronizedProcessLineByLineHandler errLineByLineHandler 439 = new SynchronizedProcessLineByLineHandler(bw, SynchronizedProcessLineByLineHandler.STDERR); 440 perlProcess.setStdOutLineByLineHandler(outLineByLineHandler); 441 perlProcess.setStdErrLineByLineHandler(errLineByLineHandler); 442 443 // GS2PerlConstructor will do further handling of exceptions that may occur during the perl 444 // process (including if writing something to the process' inputstream, not that we're doing that for this perlProcess) 445 perlProcess.setExceptionHandler(this); 446 447 // finally, execute the process 448 449 // Captures the std err of a program and pipes it into 450 // std in of java, as before. 451 452 logger.info("**** BEFORE runProcess."); 453 perlProcess.runProcess(); 454 logger.info("**** AFTER runProcess:"); 455 456 // The original runPerlCommand() code had an ineffective check for whether the cmd had been cancelled 457 // midway through executing the perl, as condition of a while loop reading from stderr and stdout. 458 // We don't include the cancel check here, as superclass CollectionConstructor.stopAction(), which set 459 // this.cancel to true, never got called anywhere. 460 // But I think a proper cancel of our perl process launched by this GS2PerlConstructor Thread object 461 // and of the worker threads it launches, could be implemented with interrupts. See: 462 // http://stackoverflow.com/questions/6859681/better-way-to-signal-other-thread-to-stop 463 // https://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html 464 // https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#interrupted() 465 // https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/ 466 // The code that calls GS2PerlConstructor.stopAction() should also call GSPerlConstructor.interrupt() 467 // Then in SafeProcess.runProcess(), I think the waitFor() will throw an InterruptedException() 468 // This can be caught and interrupt() called on SafeProcess' workerthreads, 469 // Any workerthreads' run() methods that block (IO, loops) can test this.isInterrupted() 470 // and can break out of any loops and release resources in finally. 471 // Back in SafeProcess.runProcess, the InterruptedException catch block will be followed by finally 472 // that will clear up any further resources and destroy the process forcibly if it hadn't been ended. 473 474 } catch(IOException e) { 475 e.printStackTrace(); 476 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString())); 477 } finally { 478 SafeProcess.closeResource(bw); 479 } 480 481 if (!this.cancel) { 482 // Now display final message based on exit value 483 484 if (perlProcess.getExitValue() == 0) { 485 //status = OK; 486 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success")); 487 488 success = true; 489 } else { 490 //status = ERROR; 491 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure")); 492 493 //return false; 494 success = false; 495 496 } 497 } else { // cancelled 498 499 // I need to somehow kill the child process. Unfortunately Thread.stop() and Process.destroy() both fail to do this. But now, thankx to the magic of Michaels 'close the stream suggestion', it works fine. 500 sendProcessStatus(new ConstructionEvent(this, GSStatus.HALTED, "killing the process")); 501 //prcs.getOutputStream().close(); 502 //prcs.destroy(); 503 ////status = ERROR; 504 505 //return false; 506 success = false; 507 } 508 // we're done, but we don't send a process complete message here cos there might be stuff to do after this has finished. 509 //return true; 510 return success; 511 } 512 513 514 // this method is blocking again, on Windows when adding user comments 515 protected boolean old_runPerlCommand(String[] command, String[] envvars, File dir) 378 516 { 379 517 boolean success = true; … … 406 544 command_str = command_str + command[i] + " "; 407 545 } 546 547 logger.info("### old runPerlCmd, command = " + command_str); 408 548 409 549 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str)); … … 454 594 } 455 595 } 456 closeResource(bw);596 SafeProcess.closeResource(bw); 457 597 458 598 if (!this.cancel) … … 500 640 501 641 if( prcs != null ) { 502 closeResource(prcs.getErrorStream());503 closeResource(prcs.getOutputStream());504 closeResource(prcs.getInputStream());642 SafeProcess.closeResource(prcs.getErrorStream()); 643 SafeProcess.closeResource(prcs.getOutputStream()); 644 SafeProcess.closeResource(prcs.getInputStream()); 505 645 prcs.destroy(); 506 646 } 507 647 508 closeResource(ebr);509 closeResource(stdinbr);648 SafeProcess.closeResource(ebr); 649 SafeProcess.closeResource(stdinbr); 510 650 } 511 651 … … 514 654 return success; 515 655 } 516 517 public static void closeResource(Closeable resourceHandle) { 518 try { 519 if(resourceHandle != null) { 520 resourceHandle.close(); 521 resourceHandle = null; 522 } 523 } catch(Exception e) { 524 System.err.println("Exception closing resource: " + e.getMessage()); 525 e.printStackTrace(); 526 } 656 657 658 // From interface SafeProcess.ExceptionHandler 659 // Called when an exception happens during the running of our perl process. However, 660 // exceptions when reading from our perl process' stderr and stdout streams are handled by 661 // SynchronizedProcessLineByLineHandler.gotException() below. 662 public void gotException(Exception e) { 663 664 // do what original runPerlCommand() code always did when an exception occurred 665 // when running the perl process: 666 e.printStackTrace(); 667 sendProcessStatus(new ConstructionEvent(this,GSStatus.ERROR, 668 "Exception occurred " + e.toString())); // atomic 527 669 } 670 671 // This class deals with each incoming line from the perl process' stderr or stdout streams. One 672 // instance of this class for each stream. However, since multiple instances of this LineByLineHandler 673 // could be (and in fact, are) writing to the same file in their own threads, the writer object needs 674 // to be made threadsafe. 675 // This class also handles exceptions during the running of the perl process. 676 // The runPerlCommand code originally would do a sendProcessStatus on each exception, so we ensure 677 // we do that here too, to continue original behaviour. 678 protected class SynchronizedProcessLineByLineHandler implements SafeProcess.LineByLineHandler 679 { 680 public static final int STDERR = 0; 681 public static final int STDOUT = 1; 682 683 private final int source; 684 private final BufferedWriter bwHandle; // needs to be final to synchronize on the object 685 686 687 public SynchronizedProcessLineByLineHandler(BufferedWriter bw, int src) { 688 this.bwHandle = bw; 689 this.source = src; // STDERR or STDOUT 690 } 691 692 public void gotLine(String line) { 693 //if(this.source == STDERR) { 694 ///System.err.println("ERROR: " + line); 695 //} else { 696 ///System.err.println("OUT: " + line); 697 //} 698 699 // BufferedWriter writes may not be atomic 700 // http://stackoverflow.com/questions/9512433/is-writer-an-atomic-method 701 // Choosing to put try-catch outside of sync block, since it's okay to give up lock on exception 702 // http://stackoverflow.com/questions/14944551/it-is-better-to-have-a-synchronized-block-inside-a-try-block-or-a-try-block-insi 703 // "All methods on Logger are multi-thread safe", see 704 // http://stackoverflow.com/questions/14211629/java-util-logger-write-synchronization 705 706 try { 707 synchronized(bwHandle) { // get a lock on the writer handle, then write 708 709 bwHandle.write(line + "\n"); 710 } 711 } catch(IOException ioe) { 712 String msg = (source == STDERR) ? "stderr" : "stdout"; 713 msg = "Exception when writing out a line read from perl process' " + msg + " stream."; 714 GS2PerlConstructor.logger.error(msg, ioe); 715 } 716 717 // this next method is thread safe since only synchronized methods are invoked. 718 // and only immutable (final) vars are used. 719 sendProcessStatus(new ConstructionEvent(GS2PerlConstructor.this, GSStatus.CONTINUING, line)); 720 } 721 722 // This is called when we get an exception during the processing of a perl's 723 // input-, err- or output stream 724 public void gotException(Exception e) { 725 String msg = (source == STDERR) ? "stderr" : "stdout"; 726 msg = "Got exception when processing the perl process' " + msg + " stream."; 727 728 // now do what the original runPerlCommand() code always did: 729 e.printStackTrace(); 730 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString())); // atomic 731 } 732 733 } // end inner class SynchronizedProcessLineByLineHandler 734 528 735 }
Note:
See TracChangeset
for help on using the changeset viewer.