>Number:         3847
>Category:       os-windows
>Synopsis:       CGIs don't get terminated if browser aborts connection
>Confidential:   no
>Severity:       critical
>Priority:       medium
>Responsible:    apache
>State:          open
>Class:          sw-bug
>Submitter-Id:   apache
>Arrival-Date:   Fri Feb  5 12:20:00 PST 1999
>Last-Modified:
>Originator:     [EMAIL PROTECTED]
>Organization:
apache
>Release:        1.3.4
>Environment:
Win32, v.1.3.4 compiled with VisualStudio 6.0
>Description:
1) browser places request to a CGI that has extended lifetime.
2) browser terminates connection (Stop or Escape is pressed)
3) the CGI doesn't get killed by Apache, and either eventually expires by 
itself, or hangs around forever, which represents a problem
>How-To-Repeat:
I'm using a simple c code that just slowly dumps text into stdout forever
>Fix:
According to my observations there are several issues here that contribute 
to the problem, some of them related to mod_cgi, and some of them
more general (that's why I'm filing a general Windows bug)

My understanding is that both Windows and Unix versions similarly detect that 
connection is dropped, and set r->connection->aborted to 1
1) Then Windows version gets stuck in mod_cgi.c in this place:
  while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
      continue;
  }
Basically, it attempts to read from script's stderr, and doesn't return from
the blocking ReadFile call. Unix version doesn't have this problem. Don't
know why, perhaps because it relies on SIGPIPE which doesn't work on Win32.
I circumvented this problem by using
  if(!r->connection->aborted) {
          while (ap_bgets(argsbuffer, HUGE_STRING_LEN, script_err) > 0) {
              continue;
          }
  }
I'm not really suggesting to use this fix, but it allowed me to get to 
other issues.
(Note that the thread can similarly get stuck in reading from script's stdout.
I didn't have this problem because my CGI generates bunch of data, yet that
might be a problem.)

2) Now, Unix version kills CGIs off in the child_main() in http_main.c through
calling 
   ap_clear_pool(ptrans);
which eventually gets to 
  free_proc_chain(a->subprocesses);
in the alloc.c which kills CGIs in the Unix version. There's bunch of Win32
code which seems to do the same job, but it's *never called*, because
a->subprocesses is always NULL.
My understanding is that ap_note_subprocess() which stores process id in the 
pool in Unix version is never called in Win32 code. 
To fix that, I added the ap_note_subprocess call to the ap_bspawn_child 
function :
...
            /* Setup the cleanup routine for the handle */
            ap_note_cleanups_for_h(p, hPipeErrorRead);

            /* Associate the handle with the new buffer */
            ap_bpushh(*pipe_err, hPipeErrorRead);
        }
      // Igor's change
        ap_note_subprocess(p, pid, kill_how);
    }  


    /*
     * Now that handles have been inherited, close them to be safe.
     * You don't want to read or write to them accidentally, and we
     * sure don't want to have a handle leak.
     */
    CloseHandle(hPipeOutputWrite);
    CloseHandle(hPipeInputRead);
    CloseHandle(hPipeErrorWrite);
...

3) Now we do get to the body of code that terminates CGI process in the
free_proc_chain function. However, the code that calls TerminateProcess is
buggy. The reason: CGI processes are started in util_script.c via 
the CreateProcess call. Note that the process ID is preserved as pid, and 
the process handle is immediately closed, which is fine.
However when it comes to terminating the process, this pid, i.e. Win32 process
id is used in TerminateProcess call. This is wrong, and the process handle
should be used instead. Process handle can be retrieved using process ID 
with the OpenProcess call. Several changes in the free_proc_chain did
the job for me:
...
    for (p = procs; p; p = p->next) {
      HANDLE pid = OpenProcess(PROCESS_ALL_ACCESS,
      1, p->pid);
        if(0 == pid) {
          p->kill_how = kill_never;
        } else
          CloseHandle(pid);
    }


    for (p = procs; p; p = p->next) {
        if (p->kill_how == kill_after_timeout) {
            need_timeout = 1;
        }
        else if (p->kill_how == kill_always) {
      // Igor's change
        HANDLE pid = OpenProcess(PROCESS_ALL_ACCESS,
        1, p->pid);
        if(0 != pid) {
                TerminateProcess(pid, 1);
          CloseHandle(pid);
        }
        }
    }
    /* Sleep only if we have to... */

    if (need_timeout)
        sleep(3);

    /* OK, the scripts we just timed out for have had a chance to clean up
     * --- now, just get rid of them, and also clean up the system accounting
     * goop...
     */

    for (p = procs; p; p = p->next) {
      if (p->kill_how == kill_after_timeout) {
      // Igor's change
        HANDLE pid = OpenProcess(PROCESS_ALL_ACCESS, 1, p->pid);
        if(0 != pid) {
                TerminateProcess(pid, 1);
          CloseHandle(pid);
        }
      }
    }
...
>Audit-Trail:
>Unformatted:
[In order for any reply to be added to the PR database, ]
[you need to include <[EMAIL PROTECTED]> in the Cc line ]
[and leave the subject line UNCHANGED.  This is not done]
[automatically because of the potential for mail loops. ]
[If you do not include this Cc, your reply may be ig-   ]
[nored unless you are responding to an explicit request ]
[from a developer.                                      ]
[Reply only with text; DO NOT SEND ATTACHMENTS!         ]



Reply via email to