java - example - Lanzando wkhtmltopdf desde Runtime.getRuntime(). Exec(): ¿nunca termina?
runtime getruntime() exec documentation (4)
Estoy lanzando wkhtmltopdf desde mi aplicación Java (parte de un servidor Tomcat, ejecutándose en modo de depuración dentro de Eclipse Helios en Win7 de 64 bits): me gustaría esperar a que se complete, luego Do More Stuff.
String cmd[] = {"wkhtmltopdf", htmlPathIn, pdfPathOut};
Process proc = Runtime.getRuntime().exec( cmd, null );
proc.waitFor();
Pero waitFor()
nunca regresa. Aún puedo ver el proceso en el Administrador de tareas de Windows (con la línea de comando que pasé a exec (): se ve bien). Y FUNCIONA. wkhtmltopdf produce el PDF que esperaría, justo donde lo esperaría. Puedo abrirlo, cambiarle el nombre, lo que sea, incluso mientras el proceso todavía está en ejecución (antes de que lo termine manualmente).
Desde la línea de comandos, todo está bien:
c:/wrk>wkhtmltopdf C:/Temp/foo.html c:/wrk/foo.pdf Loading pages (1/6) Counting pages (2/6) Resolving links (4/6) Loading headers and footers (5/6) Printing pages (6/6) Done
El proceso sale bien, y la vida continúa.
Entonces, ¿qué pasa con runtime.exec()
que causa que wkhtmltopdf nunca termine?
Podría tomar proc.getInputStream () y buscar "Hecho", pero eso es ... vil. Quiero algo que sea más general.
He llamado a exec () con y sin un directorio de trabajo. Lo he intentado con y sin una matriz "env" vacía. Sin alegría.
¿Por qué mi proceso está pendiente y qué puedo hacer para solucionarlo?
PD: He intentado esto con otras aplicaciones de línea de comandos, y ambos muestran el mismo comportamiento.
Otros problemas del ejecutivo.
Estoy tratando de leer error estándar y error, sin éxito. Desde la línea de comandos, sé que se supone que hay algo muy parecido a mi experiencia en línea de comandos, pero cuando leo la secuencia de entrada devuelta por proc.getInputStream (), obtengo inmediatamente un EOL (-1, estoy usando inputStream.read()
).
Revisé JavaDoc for Process, y encontré esto
El proceso principal utiliza estas secuencias para alimentar la entrada y obtener resultados del subproceso. Debido a que algunas plataformas nativas solo proporcionan un tamaño de búfer limitado para flujos de entrada y salida estándar, si no se escribe rápidamente el flujo de entrada o se lee el flujo de salida del subproceso, se puede bloquear el subproceso [b] e incluso el punto muerto [/ b].
Énfasis añadido. Así que lo intenté. La primera ''lectura ()'' en la Entrada Estándar inputStream bloqueó hasta que maté el proceso ...
CON WKHTMLTOPDF
Con la línea de comando genérica ap & no params, por lo que debe "volcar el uso y terminar", succiona el std :: out apropiado, luego finaliza.
¡Interesante!
¿Problema de la versión de JVM? Estoy usando 1.6.0_23. La última es ... v24. Acabo de consultar el registro de cambios y no veo nada prometedor, pero intentaré actualizar de todos modos.
Bueno. No dejes que las Flujos de entrada llenen o bloquearán. Comprobar. .close()
también puede evitar esto, pero no es demasiado brillante.
Eso funciona en general (incluidas las aplicaciones genéricas de línea de comandos que he probado).
En específico , sin embargo, se cae. Parece que wkhtmltopdf está usando algo de manipulación de terminal / cursor para hacer una barra de progreso ASCII-graphic. Creo que esto está causando que inputStream devuelva EOF inmediatamente en lugar de darme los valores correctos.
¿Algunas ideas? Casi no rompe el trato, pero definitivamente sería bueno tenerlo.
Tuve el mismo problema exacto que tú y yo lo resolvimos. Aquí están mis hallazgos:
Por alguna razón, la salida de wkhtmltopdf va a STDERR del proceso y NO a STDOUT. Lo he verificado llamando a wkhtmltopdf desde Java y Perl
Entonces, por ejemplo en Java, tendrías que hacer:
//ProcessBuilder is the recommended way of creating processes since Java 1.5
//Runtime.getRuntime().exec() is deprecated. Do not use.
ProcessBuilder pb = new ProcessBuilder("wkhtmltopdf.exe", htmlFilePath, pdfFilePath);
Process process = pb.start();
BufferedReader errStreamReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
//not "process.getInputStream()"
String line = errStreamReader.readLine();
while(line != null)
{
System.out.println(line); //or whatever else
line = reader.readLine();
}
Como nota al margen, si genera un proceso de java, DEBE leer de las corrientes stdout y stderr (incluso si no hace nada con él) porque, de lo contrario, el buffer de flujo se llenará y el proceso se bloqueará y nunca volverá.
Para proteger su código en el futuro, solo en caso de que los desarrolladores de wkhtmltopdf decidan escribir en stdout, puede redirigir stderr del proceso hijo a stdout y leer solo una secuencia como esta:
ProcessBuilder pb = new ProcessBuilder("wkhtmltopdf.exe", htmlFilePath, pdfFilePath);
pb.redirectErrorStream(true);
Process process = pb.start();
BufferedReader inStreamReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
En realidad, hago esto en todos los casos en que tengo que generar un proceso externo desde Java. De esa forma no tengo que leer dos flujos.
También debe leer las secuencias del proceso generado en diferentes subprocesos si no desea que se bloquee el hilo principal, ya que la lectura de las secuencias está bloqueando.
Espero que esto ayude.
ACTUALIZACIÓN : Planteé este problema en la página del proyecto y me respondieron que esto es por diseño porque wkhtmltopdf admite dar la salida pdf real en STDOUT. Por favor, mira el enlace para más detalles y código java.
Un proceso tiene 3 flujos: entrada, salida y error. puede leer la salida y el flujo de errores al mismo tiempo usando procesos separados. vea esta pregunta y su respuesta aceptada y también esta, por ejemplo.
final Semaphore semaphore = new Semaphore(numOfThreads);
final String whktmlExe = tmpwhktmlExePath;
int doccount = 0;
try{
File fileObject = new File(inputDir);
for(final File f : fileObject.listFiles()) {
if(f.getAbsolutePath().endsWith(".html")) {
doccount ++;
if(doccount >500 ) {
LOG.info(" done with conversion of 1000 docs exiting ");
break;
}
System.out.println(" inside for before "+semaphore.availablePermits());
semaphore.acquire();
System.out.println(" inside for after "+semaphore.availablePermits() + " ---" +f.getName());
new java.lang.Thread() {
public void run() {
try {
String F_ = f.getName().replaceAll(".html", ".pdf") ;
ProcessBuilder pb = new ProcessBuilder(whktmlExe , f.getAbsolutePath(), outPutDir + F_ .replaceAll(" ", "_") );//"wkhtmltopdf.exe", htmlFilePath, pdfFilePath);
pb.redirectErrorStream(true);
Process process = pb.start();
BufferedReader errStreamReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = errStreamReader.readLine();
while(line != null)
{
System.err.println(line); //or whatever else
line = errStreamReader.readLine();
}
System.out.println("after completion for ");
} catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println(" in finally releasing ");
semaphore.release();
}
}
}.start();
}
}
}catch (Exception ex) {
LOG.error(" *** Error in pdf generation *** ", ex);
}
while (semaphore.availablePermits() < numOfThreads) {//till all threads finish
LOG.info( " Waiting for all threads to exit "+ semaphore.availablePermits() + " --- " +( numOfThreads - semaphore.availablePermits()));
java.lang.Thread.sleep(10000);
}