Bug 34801 - PATCH: CGIServlet does not terminate child after a timeout
Summary: PATCH: CGIServlet does not terminate child after a timeout
Status: RESOLVED FIXED
Alias: None
Product: Tomcat 5
Classification: Unclassified
Component: Servlets:CGI (show other bugs)
Version: 5.5.17
Hardware: Other Windows XP
: P3 enhancement (vote)
Target Milestone: ---
Assignee: Tomcat Developers Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2005-05-08 06:47 UTC by chris_j_davey
Modified: 2011-04-08 17:51 UTC (History)
0 users



Attachments
This patch adds IOException trapping to CGIServlet, to ensure process termination. It also adds a watchdog timer to kill off the CGI Process after a configurable timeout. (15.52 KB, patch)
2005-05-08 06:49 UTC, chris_j_davey
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description chris_j_davey 2005-05-08 06:47:30 UTC
CGIServlet does not kill CGI child process on IOException, nor terminate child
after a timeout.

We had a problem where a CGI child process (in perl, on windows) remained
running for a long time. In some situations where the client disconnected, an
IOException was thrown in the servlet, but this did not result in a cleanup of
the CGI child process. This was a problem as our CGI program accessed a single
threaded resource over TCP, and could therefore lock the system up indefinitly.

===

--- CGIServlet.java	Sun May 08 04:46:05 2005
+++
C:\Temp\hjakarta-tomcat-5.5.9-src.tar\jakarta-tomcat-5.5.9-src\jakarta-tomcat-catalina\catalina\src\share\org\apache\catalina\servlets\CGIServlet.java
Sat Mar 26 18:24:02 2005
@@ -267,8 +267,6 @@
     private String parameterEncoding = System.getProperty("file.encoding",
                                                           "UTF-8");
 
-    private long lScriptTimeoutMillis = 2000;
-
     /** object used to ensure multiple threads don't try to expand same file */
     static Object expandFileLock = new Object();
 
@@ -332,16 +330,10 @@
             cgiExecutable = value;
         }
 
-
         value = getServletConfig().getInitParameter("parameterEncoding");
         if (value != null) {
             parameterEncoding = value;
         }
-        // Added by CJD
-        value = getServletConfig().getInitParameter("scriptTimeout");
-        if (value != null) {
-            lScriptTimeoutMillis = Integer.valueOf(value).intValue();
-        }
 
         // Identify the internal container resources we need
         context = config.getServletContext();
@@ -1440,7 +1432,7 @@
      */
 
     protected class CGIRunner {
-        private Process proc = null;
+
         /** script/command to be executed */
         private String command = null;
 
@@ -1656,6 +1648,7 @@
             InputStream cgiOutput = null;
             BufferedReader commandsStdErr = null;
             BufferedOutputStream commandsStdIn = null;
+            Process proc = null;
             int bufRead = -1;
 
             //create query arguments
@@ -1731,200 +1724,138 @@
             }
 
             rt = Runtime.getRuntime();
-            try {
+            proc = rt.exec(cmdAndArgs.toString(), hashToStringArray(env), wd);
 
-                proc = rt.exec(cmdAndArgs.toString(), hashToStringArray(env), wd);
+            if(contentStream != null) {
+                commandsStdIn = new BufferedOutputStream(proc.getOutputStream());
+                commandsStdIn.write(contentStream.toByteArray());
+                commandsStdIn.flush();
+                commandsStdIn.close();
+            }
 
-                if(contentStream != null) {
-                    commandsStdIn = new
BufferedOutputStream(proc.getOutputStream());
-                    commandsStdIn.write(contentStream.toByteArray());
-                    commandsStdIn.flush();
-                    commandsStdIn.close();
-                }
+            /* we want to wait for the process to exit,  Process.waitFor()
+             * is useless in our situation; see
+             * http://developer.java.sun.com/developer/
+             *                               bugParade/bugs/4223650.html
+             */
 
-                /* we want to wait for the process to exit,  Process.waitFor()
-                 * is useless in our situation; see
-                 * http://developer.java.sun.com/developer/
-                 *                               bugParade/bugs/4223650.html
-                 */
-
-                boolean isRunning = true;
-                commandsStdErr = new BufferedReader
-                    (new InputStreamReader(proc.getErrorStream()));
-                BufferedWriter servletContainerStdout = null;
+            boolean isRunning = true;
+            commandsStdErr = new BufferedReader
+                (new InputStreamReader(proc.getErrorStream()));
+            BufferedWriter servletContainerStdout = null;
 
-                try {
-                    if (response.getOutputStream() != null) {
-                        servletContainerStdout =
-                            new BufferedWriter(new OutputStreamWriter
-                                (response.getOutputStream()));
-                    }
-                } catch (IOException ignored) {
-                    //NOOP: no output will be written
+            try {
+                if (response.getOutputStream() != null) {
+                    servletContainerStdout =
+                        new BufferedWriter(new OutputStreamWriter
+                            (response.getOutputStream()));
                 }
-                final BufferedReader stdErrRdr = commandsStdErr ;
+            } catch (IOException ignored) {
+                //NOOP: no output will be written
+            }
+            final BufferedReader stdErrRdr = commandsStdErr ;
 
-                new Thread() {
-                        public void run () {
-                            sendToLog(stdErrRdr) ;
-                        } ;
-                    }.start() ;
-
-
-                new Thread() {
-                        public void run () {
-                            deadProcWatcher(proc,lScriptTimeoutMillis) ;
-                        } ;
-                    }.start() ;
-
-                InputStream cgiHeaderStream =
-                    new HTTPHeaderInputStream(proc.getInputStream());
-                BufferedReader cgiHeaderReader =
-                    new BufferedReader(new InputStreamReader(cgiHeaderStream));
-                boolean isBinaryContent = false;
+            new Thread() {
+                public void run () {
+                    sendToLog(stdErrRdr) ;
+                } ;
+            }.start() ;
+
+            InputStream cgiHeaderStream =
+                new HTTPHeaderInputStream(proc.getInputStream());
+            BufferedReader cgiHeaderReader =
+                new BufferedReader(new InputStreamReader(cgiHeaderStream));
+            boolean isBinaryContent = false;
             
-
-
-                while (isRunning) {
-                    try {
-                        //set headers
-                        String line = null;
-                        while (((line = cgiHeaderReader.readLine()) != null)
-                               && !("".equals(line))) {
-                            if (debug >= 2) {
-                                log("runCGI: addHeader(\"" + line + "\")");
-                            }
-                            if (line.startsWith("HTTP")) {
-                                //TODO: should set status codes (NPH support)
-                                /*
-                                 * response.setStatus(getStatusCode(line));
-                                 */
-                            } else if (line.indexOf(":") >= 0) {
-                                String header =
-                                    line.substring(0, line.indexOf(":")).trim();
-                                String value =
-                                    line.substring(line.indexOf(":") + 1).trim(); 
-                                response.addHeader(header , value);
-                                if ((header.toLowerCase().equals("content-type"))
-                                    && (!value.toLowerCase().startsWith("text"))) {
-                                    isBinaryContent = true;
-                                }
-                            } else {
-                                log("runCGI: bad header line \"" + line + "\"");
+            while (isRunning) {
+                try {
+                    //set headers
+                    String line = null;
+                    while (((line = cgiHeaderReader.readLine()) != null)
+                           && !("".equals(line))) {
+                        if (debug >= 2) {
+                            log("runCGI: addHeader(\"" + line + "\")");
+                        }
+                        if (line.startsWith("HTTP")) {
+                            //TODO: should set status codes (NPH support)
+                            /*
+                             * response.setStatus(getStatusCode(line));
+                             */
+                        } else if (line.indexOf(":") >= 0) {
+                            String header =
+                                line.substring(0, line.indexOf(":")).trim();
+                            String value =
+                                line.substring(line.indexOf(":") + 1).trim(); 
+                            response.addHeader(header , value);
+                            if ((header.toLowerCase().equals("content-type"))
+                                && (!value.toLowerCase().startsWith("text"))) {
+                                isBinaryContent = true;
                             }
+                        } else {
+                            log("runCGI: bad header line \"" + line + "\"");
                         }
+                    }
 
-                        //write output
-                        if (isBinaryContent) {
-                            byte[] bBuf = new byte[2048];
-                            OutputStream out = response.getOutputStream();
-                            cgiOutput = proc.getInputStream();
-                            while ((bufRead = cgiOutput.read(bBuf)) != -1) {
-                                if (debug >= 4) {
-                                    log("runCGI: output " + bufRead +
-                                        " bytes of binary data");
-                                }
-                                out.write(bBuf, 0, bufRead);
+                    //write output
+                    if (isBinaryContent) {
+                        byte[] bBuf = new byte[2048];
+                        OutputStream out = response.getOutputStream();
+                        cgiOutput = proc.getInputStream();
+                        while ((bufRead = cgiOutput.read(bBuf)) != -1) {
+                            if (debug >= 4) {
+                                log("runCGI: output " + bufRead +
+                                    " bytes of binary data");
                             }
-                        } else {
-                            commandsStdOut = new BufferedReader
-                                (new InputStreamReader(proc.getInputStream()));
+                            out.write(bBuf, 0, bufRead);
+                        }
+                    } else {
+                        commandsStdOut = new BufferedReader
+                            (new InputStreamReader(proc.getInputStream()));
 
-                            char[] cBuf = new char[1024];
-                            try {
-                                while ((bufRead = commandsStdOut.read(cBuf)) !=
-1) {
-                                    if (servletContainerStdout != null) {
-                                        if (debug >= 4) {
-                                            log("runCGI: write(\"" +
+                        char[] cBuf = new char[1024];
+                        try {
+                            while ((bufRead = commandsStdOut.read(cBuf)) != -1) {
+                                if (servletContainerStdout != null) {
+                                    if (debug >= 4) {
+                                        log("runCGI: write(\"" +
                                                 new String(cBuf, 0, bufRead) +
"\")");
-                                        }
-                                        servletContainerStdout.write(cBuf, 0,
bufRead);
                                     }
-                                }
-                            } finally {
-                                // Attempt to consume any leftover byte if
something bad happens,
-                                // such as a socket disconnect on the servlet
side; otherwise, the
-                                // external process could hang
-                                if (bufRead != -1) {
-                                    while ((bufRead =
commandsStdOut.read(cBuf)) != -1) {}
+                                    servletContainerStdout.write(cBuf, 0, bufRead);
                                 }
                             }
-    
-                            if (servletContainerStdout != null) {
-                                servletContainerStdout.flush();
+                        } finally {
+                            // Attempt to consume any leftover byte if
something bad happens,
+                            // such as a socket disconnect on the servlet side;
otherwise, the
+                            // external process could hang
+                            if (bufRead != -1) {
+                                while ((bufRead = commandsStdOut.read(cBuf)) !=
-1) {}
                             }
                         }
-
-                        proc.exitValue(); // Throws exception if alive
-
-                        isRunning = false;
-
-                    } catch (IllegalThreadStateException e) {
-                        try {
-                            Thread.sleep(500);
-                        } catch (InterruptedException ignored) {
+    
+                        if (servletContainerStdout != null) {
+                            servletContainerStdout.flush();
                         }
                     }
-               
-                } //replacement for Process.waitFor()
-                // Close the output stream used
-                if (isBinaryContent) {
-                    cgiOutput.close();
-                } else {
-                    commandsStdOut.close();
-                }
-            }
-            catch (IOException e){
-                log ("Caught exception " + e);
-                System.out.println("Caught IOException - printing dump 1");
-                e.printStackTrace();
-                if (proc != null){
-                    proc.destroy();
-                    proc = null;
-                }
-                throw new IOException (e.toString());
-            }
-            finally{
-                log ("Running finally block");
-                if (proc != null){
-                    proc.destroy();
-                    proc = null;
-                }
-            }
-        }
 
-        private void deadProcWatcher(Process myproc,long lWait){
-            long processRunDeadline = System.currentTimeMillis() +lWait;
+                    proc.exitValue(); // Throws exception if alive
+
+                    isRunning = false;
 
-            while(System.currentTimeMillis() < processRunDeadline){
-                log("deadProcWatcher: cgi run:  " + 
-                    processRunDeadline + " " +
-                    System.currentTimeMillis() +
-                    " " + myproc);
-                try{
-                    myproc.exitValue(); // Throws exception if alive
-                    log("deadProcWatcher: exiting normally");
-                    return; // child has exited, we can exit
-                    
                 } catch (IllegalThreadStateException e) {
                     try {
                         Thread.sleep(500);
                     } catch (InterruptedException ignored) {
                     }
                 }
+            } //replacement for Process.waitFor()
+            // Close the output stream used
+            if (isBinaryContent) {
+                cgiOutput.close();
+            } else {
+                commandsStdOut.close();
             }
-            if (System.currentTimeMillis() > processRunDeadline){
-                log("Killing process due to timeout" + 
-                    processRunDeadline + " " +
-                    System.currentTimeMillis() +
-                    " " + myproc);
-                myproc.destroy();
-            }
-            
         }
 
-        
-        
         private void sendToLog(BufferedReader rdr) {
             String line = null;
             int lineCount = 0 ;
Comment 1 chris_j_davey 2005-05-08 06:49:21 UTC
Created attachment 14964 [details]
This patch adds IOException trapping to CGIServlet, to ensure process termination. It also adds a watchdog timer to kill off the CGI Process after a configurable timeout.
Comment 2 william.barker 2006-04-17 01:13:54 UTC
changing component.
Comment 3 Mark Thomas 2006-07-22 00:35:25 UTC
I have applied the IOException part of the patch but left the watchdog element
for future consideration. I have changed the severity and summary accordingly.
Comment 4 Yoav Shapira 2007-03-25 13:52:29 UTC
My vote is -0 on the timeout.  Mark, if you feel like doing this, great, I won't
stand in the way.  But if not, let's close this Bugzilla issue.
Comment 5 Mark Thomas 2011-04-08 17:51:10 UTC
Closing this as fixed without applying the watchdog changes.