I've configured a PDF printer that uses Ghostscript to convert the document to a PDF, which is then processed and used by my Java desktop application. It redirects the printer data via a RedMon port. For most documents I print, it works fine and produces the PDF file as expected. However, with documents with a certain number of pages, the process simply freezes: no error is thrown, the process simply holds. It seems independent of filesize or printer properties (though the latter seems to influence the number of pages that do get printed).
After stopping the Java application, I'm left with a document with a fixed number of pages (usually 265 pages, but it also happened to end with 263 pages or 247 pages). The second to last page is incomplete (as in, partially-printed tables and texts), whereas the last page prints as an error:
ERROR: syntaxerror
OFFENDING COMMAND: --nostringval--
STACK:
/[NUMBER]
Where [NUMBER] is any given single-digit number.
Here is my Ghostscript integrator class:
public class GhostScriptIntegrator {
public static void createPDF(String[] args, String filename) {
if (args.length > 0) {
try {
Process process = Runtime.getRuntime().exec(
args[0] + " -sOutputFile=\"" + filename
+ "\" -c save pop -f -");
OutputStream os = process.getOutputStream();
BufferedReader sc = null;
try (PrintWriter writer = new PrintWriter(os)) {
sc = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = sc.readLine()) != null) {
writer.println(line);
}
writer.flush();
} catch (Exception ex) {
Logger.getLogger(GhostScriptIntegrator.class.getName()).log(Level.SEVERE, null, ex);
} finally {
if (sc != null) {
sc.close();
}
}
process.waitFor();
} catch (InterruptedException | IOException ex) {
Logger.getLogger(GhostScriptIntegrator.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
The args
parameter is handled by my virtual printer (similarly to how it was presented in my previous post):
Full argument:
-jar "C:\Program Files (x86)\Impressora SPE\ImpressoraSPE.jar" "C:\Program Files (x86)\gs\gs9.21\bin\gswin32c -I\"C:\Program Files (x86)\gs\gs9.21\lib\" -dSAFER -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sPAPERSIZE=a4 -q -dPDFA=2 -dPDFACompatibilityPolicy=1 -dSimulateOverprint=true -dCompatibilityLevel=1.3 -dPDFSETTINGS=/screen -dEmbedAllFonts=true -dSubsetFonts=true -dAutoRotatePages=/None -dColorImageDownsampleType=/Bicubic -dColorImageResolution=150"
I have a second virtual printer that works perfectly, and there seems to be no significant difference between them: same drivers, same port arguments, same setup, very similar code. Yet, it does not freeze after a certain number of pages, and the output file is as expected.
What's causing my printer to stop responding?
It turns out there is no problem with your printer, but rather with your code. More specifically, how you [do not] handle the Runtime streams. What your process is missing is a StreamGobbler.
A StreamGobbler is an InputStream that uses an internal worker thread to constantly consume input from another InputStream. It uses a buffer to store the consumed data. The buffer size is automatically adjusted, if needed.
Your process hangs because it cannot fully read the input stream. The following articles provide a very in-depth explanation as to why it happens and how to fix it:
When Runtime.exec() won't - Part 1
When Runtime.exec() won't - Part 2
But to quote the article itself (which, in turn, quotes the JDK Javadoc):
Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.
The solution is to simply exhaust each input stream from your process by implementing a StreamGobbler class:
public class GhostScriptIntegrator {
public static void createPDF(String[] args, String filename) throws FileNotFoundException {
if (args.length > 0) {
try {
Process process = Runtime.getRuntime().exec(
args[0] + " -sOutputFile=\"" + filename
+ "\" -c save pop -f -");
OutputStream os = process.getOutputStream();
BufferedReader sc = null;
InputStreamReader ir = new InputStreamReader(System.in);
try (PrintWriter writer = new PrintWriter(os)) {
StreamGobbler errorGobbler = new StreamGobbler(
process.getErrorStream(), "ERROR");
StreamGobbler outputGobbler = new StreamGobbler(
process.getInputStream(), "OUTPUT");
errorGobbler.start();
outputGobbler.start();
sc = new BufferedReader(ir);
String line;
while ((line = sc.readLine()) != null) {
writer.println(line);
writer.flush();
}
} catch (IOException ex) {
Logger.getLogger(GhostScriptIntegrator.class.getName()).log(Level.SEVERE, null, ex);
} finally {
if (sc != null) {
sc.close();
}
ir.close();
if (os != null) {
os.close();
}
}
process.waitFor();
} catch (InterruptedException | IOException ex) {
Logger.getLogger(GhostScriptIntegrator.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
class StreamGobbler extends Thread {
InputStream is;
String type;
StreamGobbler(InputStream is, String type) {
this.is = is;
this.type = type;
}
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
long contador = 0;
while (br.readLine() != null) {
//Do nothing
}
} catch (IOException ex) {
Logger.getLogger(StreamGobbler.class.getName()).log(Level.SEVERE, null, ex);
}
}
}