source: main/trunk/greenstone3/src/java/org/greenstone/gsdl3/build/GS2PerlConstructor.java@ 31574

Last change on this file since 31574 was 31574, checked in by ak19, 7 years ago

Trying to correct the GS2PerlConstructor.runPerlCommand() function which has always been brittle, and causing a deadlocked Process when things go wrong. The current solution is with the new class SafeProcess, which tries to properly create, run and end a process and manage its streams, and close them off. Have tried to ensure there will be no concurrency issues. Maybe it would be better to use Executor to instantiate threads (and to use its cancel abilities if we want this in future), but have stuck to existing code that worked. Changed GS2PerlConstructor to use this new SafeProcess class in its new runPerlCommand() method. Tested document editing and adding user comments on Linux to find these things still work. However, the deadlock was happening on windows, so that's where the solution should be tested. Leaving in debug statements for windows testing.

  • Property svn:keywords set to Author Date Id Revision
File size: 25.8 KB
Line 
1package org.greenstone.gsdl3.build;
2
3// greenstome classes
4import org.greenstone.gsdl3.util.*;
5import org.greenstone.util.Misc;
6import org.greenstone.util.GlobalProperties;
7import org.greenstone.util.SafeProcess;
8
9// xml classes
10import org.w3c.dom.Element;
11import org.w3c.dom.NodeList;
12
13//general java classes
14import java.io.BufferedReader;
15import java.io.BufferedWriter;
16import java.io.Closeable;
17import java.io.FileWriter;
18import java.io.InputStreamReader;
19import java.io.File;
20import java.io.IOException;
21import java.util.ArrayList;
22import java.util.Vector;
23
24import org.apache.log4j.*;
25
26/**
27 * CollectionConstructor class for greenstone 2 compatible building it uses the
28 * perl scripts to do the building stuff
29 */
30public class GS2PerlConstructor extends CollectionConstructor implements SafeProcess.ExceptionHandler
31{
32 static Logger logger = Logger.getLogger(org.greenstone.gsdl3.build.GS2PerlConstructor.class.getName());
33
34 public static final int NEW = 0;
35 public static final int IMPORT = 1;
36 public static final int BUILD = 2;
37 public static final int ACTIVATE = 3;
38 public static final int MODIFY_METADATA_SERVER = 4;
39
40 /**
41 * gsdlhome for greenstone 2 - we use the perl modules and building scripts
42 * from there
43 */
44 protected String gsdl2home = null;
45 /** gsdlhome for gsdl3 - shouldn't need this eventually ?? */
46 protected String gsdl3home = null;
47 /** gsdlos for greenstone 2 */
48 protected String gsdlos = null;
49 /** the path environment variable */
50 protected String path = null;
51
52 public GS2PerlConstructor(String name)
53 {
54 super(name);
55 }
56
57 /** retrieves the necessary environment variables */
58 public boolean configure()
59 {
60 // try to get the environment variables
61 this.gsdl3home = GlobalProperties.getGSDL3Home();
62 this.gsdl2home = this.gsdl3home + File.separator + ".." + File.separator + "gs2build";
63 this.gsdlos = Misc.getGsdlOS();
64
65 this.path = System.getenv("PATH");
66
67 if (this.gsdl2home == null)
68 {
69 System.err.println("You must have gs2build installed, and GSDLHOME set for GS2Perl building to work!!");
70 return false;
71 }
72 if (this.gsdl3home == null || this.gsdlos == null)
73 {
74 System.err.println("You must have GSDL3HOME and GSDLOS set for GS2Perl building to work!!");
75 return false;
76 }
77 if (this.path == null)
78 {
79 System.err.println("You must have the PATH set for GS2Perl building to work!!");
80 return false;
81 }
82 return true;
83 }
84
85 public void run()
86 {
87 String msg;
88 ConstructionEvent evt;
89 if (this.process_type == -1)
90 {
91 msg = "Error: you must set the action type";
92 evt = new ConstructionEvent(this, GSStatus.ERROR, msg);
93 sendMessage(evt);
94 return;
95 }
96 if (this.site_home == null)
97 {
98 msg = "Error: you must set site_home";
99 evt = new ConstructionEvent(this, GSStatus.ERROR, msg);
100 sendMessage(evt);
101 return;
102 }
103 if (this.process_type != NEW && this.collection_name == null)
104 {
105 msg = "Error: you must set collection_name";
106 evt = new ConstructionEvent(this, GSStatus.ERROR, msg);
107 sendMessage(evt);
108 return;
109 }
110
111 switch (this.process_type)
112 {
113 case NEW:
114 newCollection();
115 break;
116 case IMPORT:
117 importCollection();
118 break;
119 case BUILD:
120 buildCollection();
121 break;
122 case ACTIVATE:
123 activateCollection();
124 break;
125 case MODIFY_METADATA_SERVER:
126 modifyMetadataForCollection();
127 break;
128 default:
129 msg = "wrong type of action specified!";
130 evt = new ConstructionEvent(this, GSStatus.ERROR, msg);
131 sendMessage(evt);
132 break;
133 }
134 }
135
136 protected void newCollection()
137 {
138 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "Collection construction: new collection."));
139 Vector<String> command = new Vector<String>();
140 command.add("gs2_mkcol.pl");
141 command.add("-site");
142 command.add(this.site_home);
143 command.add("-collectdir");
144 command.add(GSFile.collectDir(this.site_home));
145 command.addAll(extractParameters(this.process_params));
146 command.add(this.collection_name);
147 String[] command_str = {};
148 command_str = command.toArray(command_str);
149 if (runPerlCommand(command_str))
150 {
151 // success!! - need to send the final completed message
152 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
153 } // else an error message has already been sent, do nothing
154
155 }
156
157 protected void importCollection()
158 {
159 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "Collection construction: import collection."));
160 Vector<String> command = new Vector<String>();
161
162 String perlPath = GlobalProperties.getProperty("perl.path", "perl");
163 if (perlPath.charAt(perlPath.length() - 1) != File.separatorChar)
164 {
165 perlPath = perlPath + File.separator;
166 }
167
168 command.add(perlPath + "perl");
169 command.add("-S");
170 command.add(GlobalProperties.getGS2Build() + File.separator + "bin" + File.separator + "script" + File.separator + "import.pl");
171 if (this.manifest_file != null)
172 {
173 command.add("-keepold");
174 command.add("-manifest");
175 command.add(this.manifest_file);
176 }
177 command.add("-site");
178 command.add(this.site_name);
179 command.add("-collectdir");
180 command.add(GSFile.collectDir(this.site_home));
181 command.addAll(extractParameters(this.process_params));
182 command.add(this.collection_name);
183 String[] command_str = {};
184 command_str = command.toArray(command_str);
185
186 if (runPerlCommand(command_str))
187 {
188 // success!! - need to send the final completed message
189 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
190 } // else an error message has already been sent, do nothing
191 }
192
193 protected void buildCollection()
194 {
195 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "Collection construction: build collection."));
196 Vector<String> command = new Vector<String>();
197
198 String perlPath = GlobalProperties.getProperty("perl.path", "perl");
199 if (perlPath.charAt(perlPath.length() - 1) != File.separatorChar)
200 {
201 perlPath = perlPath + File.separator;
202 }
203
204 command.add(perlPath + "perl");
205 command.add("-S");
206 command.add(GlobalProperties.getGS2Build() + File.separator + "bin" + File.separator + "script" + File.separator + "incremental-buildcol.pl");
207 command.add("-incremental");
208 command.add("-builddir");
209 command.add(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator +"index");
210 command.add("-site");
211 command.add(this.site_name);
212 command.add("-collectdir");
213 command.add(GSFile.collectDir(this.site_home));
214// command.add("-removeold"); // saves some seconds processing time when this flag's added in explicitly
215 command.addAll(extractParameters(this.process_params));
216 command.add(this.collection_name);
217
218 String[] command_str = {};
219 command_str = command.toArray(command_str);
220
221 if (runPerlCommand(command_str))
222 {
223 // success!! - need to send the final completed message
224 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
225 }// else an error message has already been sent, do nothing
226 }
227
228 protected void activateCollection()
229 {
230 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "Collection construction: activate collection."));
231
232 // first check that we have a building directory
233 // (don't want to bother running activate.pl otherwise)
234 File build_dir = new File(GSFile.collectionIndexDir(this.site_home, this.collection_name));
235 if (!build_dir.exists())
236 {
237 sendMessage(new ConstructionEvent(this, GSStatus.ERROR, "build dir doesn't exist!"));
238 return;
239 }
240
241 /*
242
243 // move building to index
244 File index_dir = new File(GSFile.collectionIndexDir(this.site_home, this.collection_name));
245 if (index_dir.exists())
246 {
247 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "deleting index directory"));
248 GSFile.deleteFile(index_dir);
249 if (index_dir.exists())
250 {
251 sendMessage(new ConstructionEvent(this, GSStatus.ERROR, "index directory still exists!"));
252 return;
253 }
254 }
255
256 GSFile.moveDirectory(build_dir, index_dir);
257 if (!index_dir.exists())
258 {
259 sendMessage(new ConstructionEvent(this, GSStatus.ERROR, "index dir wasn't created!"));
260 }
261
262 // success!! - need to send the final completed message
263 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
264 */
265
266 // Running activate.pl instead of making java move building to index as above
267 // circumvents the issue of the jdbm .lg log file (managed by TransactionManager)
268 // in index dir not getting deleted at times. The perl code is able to delete this
269 // sucessfully consistently during testing, whereas java at times is unable to delete it.
270 Vector<String> command = new Vector<String>();
271
272 String perlPath = GlobalProperties.getProperty("perl.path", "perl");
273 if (perlPath.charAt(perlPath.length() - 1) != File.separatorChar)
274 {
275 perlPath = perlPath + File.separator;
276 }
277
278 command.add(perlPath + "perl");
279 command.add("-S");
280 command.add(GlobalProperties.getGS2Build() + File.separator + "bin" + File.separator + "script" + File.separator + "activate.pl");
281 command.add("-incremental");
282 command.add("-builddir");
283 command.add(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator +"index");
284 command.add("-site");
285 command.add(this.site_name);
286 command.add("-collectdir");
287 command.add(GSFile.collectDir(this.site_home));
288// command.add("-removeold"); // saves some seconds processing time when this flag's added in explicitly. Shouldn't be added for incremental building
289 command.add("-skipactivation"); // gsdl3/util/GS2Construct does the activation and reactivation
290 command.addAll(extractParameters(this.process_params));
291 command.add(this.collection_name);
292
293 String[] command_str = {};
294 command_str = command.toArray(command_str);
295
296 if (runPerlCommand(command_str))
297 {
298 // success!! - need to send the final completed message
299 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
300 }// else an error message has already been sent, do nothing
301
302 }
303
304
305 protected void modifyMetadataForCollection()
306 {
307 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "Collection metadata: modifyMetadata (set or remove meta) for collection."));
308
309 Vector<String> command = new Vector<String>();
310
311 String perlPath = GlobalProperties.getProperty("perl.path", "perl");
312 if (perlPath.charAt(perlPath.length() - 1) != File.separatorChar)
313 {
314 perlPath = perlPath + File.separator;
315 }
316
317 String cgi_directory = GlobalProperties.getGSDL3Home() + File.separator + "WEB-INF" + File.separator + "cgi";
318 command.add(perlPath + "perl");
319 command.add("-S");
320 //command.add(GlobalProperties.getGSDL3Home() + File.separator + "WEB-INF" + File.separator + "cgi" + File.separator + "metadata-server.pl");
321 command.add(cgi_directory + File.separator + "metadata-server.pl");
322
323 // Need to set QUERY_STRING and REQUEST_METHOD=GET in environment
324 // http://www.cgi101.com/class/ch3/text.html
325 String[] envvars = {
326 "QUERY_STRING=" + this.query_string,
327 "REQUEST_METHOD=GET"
328 };
329
330 String[] command_str = {};
331 command_str = command.toArray(command_str);
332
333 // http://www.cgi101.com/class/ch3/text.html
334 // setenv QUERY_STRING and REQUEST_METHOD = GET.
335 if (runPerlCommand(command_str, envvars, new File(cgi_directory)))
336 //new File(GlobalProperties.getGSDL3Home() + File.separator + "WEB-INF" + File.separator + "cgi")))
337 {
338 // success!! - need to send the final completed message
339 sendProcessComplete(new ConstructionEvent(this, GSStatus.COMPLETED, ""));
340 }// else an error message has already been sent, do nothing
341
342 }
343
344 /** extracts all the args from the xml and returns them in a Vector */
345 protected Vector<String> extractParameters(Element param_list)
346 {
347
348 Vector<String> args = new Vector<String>();
349 if (param_list == null)
350 {
351 return args; // return an empty vector
352 }
353 NodeList params = param_list.getElementsByTagName(GSXML.PARAM_ELEM);
354
355 for (int i = 0; i < params.getLength(); i++)
356 {
357 Element p = (Element) params.item(i);
358 String name = p.getAttribute(GSXML.NAME_ATT);
359 String value = p.getAttribute(GSXML.VALUE_ATT);
360 if (!name.equals(""))
361 {
362 args.add("-" + name);
363 if (!value.equals(""))
364 {
365 args.add(value);
366 }
367 }
368 }
369
370 return args;
371 }
372
373 /** returns true if completed correctly, false otherwise */
374 protected boolean runPerlCommand(String[] command) {
375 return runPerlCommand(command, null, null);
376 }
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)
516 {
517 boolean success = true;
518
519 int sepIndex = this.gsdl3home.lastIndexOf(File.separator);
520 String srcHome = this.gsdl3home.substring(0, sepIndex);
521
522 ArrayList<String> args = new ArrayList<String>();
523 args.add("GSDLHOME=" + this.gsdl2home);
524 args.add("GSDL3HOME=" + this.gsdl3home);
525 args.add("GSDL3SRCHOME=" + srcHome);
526 args.add("GSDLOS=" + this.gsdlos);
527 args.add("GSDL-RUN-SETUP=true");
528 args.add("PERL_PERTURB_KEYS=0");
529
530 if(envvars != null) {
531 for(int i = 0; i < envvars.length; i++) {
532 args.add(envvars[i]);
533 }
534 }
535
536 for (String a : System.getenv().keySet())
537 {
538 args.add(a + "=" + System.getenv(a));
539 }
540
541 String command_str = "";
542 for (int i = 0; i < command.length; i++)
543 {
544 command_str = command_str + command[i] + " ";
545 }
546
547logger.info("### old runPerlCmd, command = " + command_str);
548
549 sendMessage(new ConstructionEvent(this, GSStatus.INFO, "command = " + command_str));
550 Process prcs = null;
551 BufferedReader ebr = null;
552 BufferedReader stdinbr = null;
553 try
554 {
555 Runtime rt = Runtime.getRuntime();
556 sendProcessBegun(new ConstructionEvent(this, GSStatus.ACCEPTED, "starting"));
557 prcs = (dir == null)
558 ? rt.exec(command, args.toArray(new String[args.size()]))
559 : rt.exec(command, args.toArray(new String[args.size()]), dir);
560
561 InputStreamReader eisr = new InputStreamReader(prcs.getErrorStream());
562 InputStreamReader stdinisr = new InputStreamReader(prcs.getInputStream());
563 ebr = new BufferedReader(eisr);
564 stdinbr = new BufferedReader(stdinisr);
565 // Captures the std err of a program and pipes it into
566 // std in of java
567
568 File logDir = new File(GSFile.collectDir(this.site_home) + File.separator + this.collection_name + File.separator + "log");
569 if (!logDir.exists())
570 {
571 logDir.mkdir();
572 }
573
574 BufferedWriter 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"));
575 bw.write("Document Editor Build \n");
576
577 bw.write("Command = " + command_str + "\n");
578
579 String eline = null;
580 String stdinline = null;
581 while (((eline = ebr.readLine()) != null || (stdinline = stdinbr.readLine()) != null) && !this.cancel)
582 {
583 if (eline != null)
584 {
585 //System.err.println("ERROR: " + eline);
586 bw.write(eline + "\n");
587 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, eline));
588 }
589 if (stdinline != null)
590 {
591 //System.err.println("OUT: " + stdinline);
592 bw.write(stdinline + "\n");
593 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, stdinline));
594 }
595 }
596 SafeProcess.closeResource(bw);
597
598 if (!this.cancel)
599 {
600 // Now display final message based on exit value
601 prcs.waitFor();
602
603 if (prcs.exitValue() == 0)
604 {
605 //status = OK;
606 sendProcessStatus(new ConstructionEvent(this, GSStatus.CONTINUING, "Success"));
607
608 success = true;
609 }
610 else
611 {
612 //status = ERROR;
613 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Failure"));
614
615 //return false;
616 success = false;
617
618 }
619 }
620 else
621 {
622 // 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.
623 sendProcessStatus(new ConstructionEvent(this, GSStatus.HALTED, "killing the process"));
624 //prcs.getOutputStream().close();
625 //prcs.destroy();
626 ////status = ERROR;
627
628 //return false;
629 success = false;
630 }
631 }
632 catch (Exception e)
633 {
634 e.printStackTrace();
635 sendProcessStatus(new ConstructionEvent(this, GSStatus.ERROR, "Exception occurred " + e.toString()));
636 } finally {
637 // http://steveliles.github.io/invoking_processes_from_java.html
638 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
639 // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec
640
641 if( prcs != null ) {
642 SafeProcess.closeResource(prcs.getErrorStream());
643 SafeProcess.closeResource(prcs.getOutputStream());
644 SafeProcess.closeResource(prcs.getInputStream());
645 prcs.destroy();
646 }
647
648 SafeProcess.closeResource(ebr);
649 SafeProcess.closeResource(stdinbr);
650 }
651
652 // we're done, but we don't send a process complete message here cos there might be stuff to do after this has finished.
653 //return true;
654 return success;
655 }
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
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
735}
Note: See TracBrowser for help on using the repository browser.