http://qs321.pair.com?node_id=362498

goofball has asked for the wisdom of the Perl Monks concerning the following question:

When a CGI script is right in the middle printing output to the browser and you hit "stop" or "cancel" on the browser, the script obviously stops running.

Is there a way to detect when this happens?
(Using an END block doesn't seem to work).

I have a script that creates a temporary zip file based on parameters passed to the script, then serves the file, then deletes it. If the script gets cancelled from the browser half-way through, the temporary zip file doesn't get deleted and sits there hogging disk space.

Any help is appreciated! Thanks!

Replies are listed 'Best First'.
Re: cleanup a cancelled CGI script
by sgifford (Prior) on Jun 08, 2004 at 19:00 UTC
    IIRC, the script doesn't actually die when the user hits STOP on their Web browser; I'm not aware of any mechanism to inform the script of this. Instead, when the script tries to send data back to the user, it sees that the socket has been closed, and receives a SIGPIPE. I believe that installing a signal handler for this signal will allow you to detect when this has happened:
    $SIG{PIPE} = sub { die "Connection closed while processing!\n" }

      If you take the following approach in addition, you've got a nice way to handle those issues and others as well.

      eval { do_stuff( ... ); 1; } or cleanup( ... )
Re: cleanup a cancelled CGI script
by iburrell (Chaplain) on Jun 08, 2004 at 19:12 UTC
    On Unix, the CGI script is attached to the web server through a pipe or socket. A write to a closed pipe or socket produces a SIGPIP signale that by default kills your program. It is possible to safely cleanup by adding a signale handler.
    $SIG{PIPE} = sub { die "Pipe error"; }
    By turning the signal into a die, the error is logged and END blocks will be run.
Re: cleanup a cancelled CGI script
by saskaqueer (Friar) on Jun 08, 2004 at 19:32 UTC

    I tried the solution of using SIGPIPE posted by others above on Windows XP just to try it. Of course, there is no SIGPIPE sent to the script when running under Win32 (well, WinXP anyhow). What happened in my test is that the script doesn't even realize that the client has disconnected. The script finishes running normally, with no change in execution (in a simple test anyhow). Even adding a or die("print failed: $!"); to a print statement that should fail doesn't change anything. My test script below:

    #!perl -w $| = 1; BEGIN { $SIG{PIPE} = sub { die "Pipe error: @_\n" }; } use strict; use CGI; my $q = CGI->new(); print $q->header(); print "output #1\n"; sleep 15; # we stop the browser load during the 15 sec. sleep print "output #2\n" or die("print failed: $!"); END { open my $fh, '>>', 'debug.txt'; print $fh "We made it to the END block.\n"; close $fh; }
      I think (on Windows, anyway) that you'd have to have a sweeper (reaper?) process that comes along every hour or so looking for ZIP files that are older than, say, one hour, and assume that they are incomplete, and delete them. Or you could add them to a database, or send them to another process, whose job it is delete any ZIPs in its list (or database) that are older than x hours.
        Why not just do it in the script when it runs?
        my $tmp_dir = "/path/to/tmpdir"; opendir(TMPDIR,$tmp_dir) || die $!; -M "$tmp_dir/$_" > 1/24 and unlink "$tmp_dir/$_" for (grep /zip/, read +dir(TMPDIR)); closedir(TMPDIR);
        or similar (untested).

        cLive ;-)

        ++ That's what I would've recommeneded. (Or, similarly, use one of the Win32::Process and on startup, put pid into database, then when pid dies, have a second process kill the pid related to the .zip file...)


        ----
        Zak - the office
      You don't specify what web server is running your script. I suspect that Apache will do the same thing on Win32 as *nix, if possible, but that IIS will do its own thing, which probably isn't SIGPIPE. To address the OP's question: if the only concern from these cancelled scripts is stray .ZIP files that need to be deleted, there's a non-Perl solution that you might find useful. You could give the .ZIP files their own temp directory to live in, and use cron to run tmpwatch on that directory. Files that have sat around too long are cleaned up automatically.

      --
      Spring: Forces, Coiled Again!
      $SIG{PIPE} won't work on Windows. Windows does not implement signals, pipes, and sockets in the same way. The way CGI scripts communicate with the server is different and does not result in a SIGPIPE when the communications is disconnected.

      I don't see the problem if the script finishes normally even if the client disconnects. Can't it delete the temporary file in its normal cleanup? Special code is only needed when the disconnect causes the CGI scripts to be killed before it is done and not run the END code.

Re: cleanup a cancelled CGI script
by goofball (Acolyte) on Jun 08, 2004 at 20:40 UTC

    I'm running the script on a Unix-based FreeBSD system running the Apache 1.3.27 server.

    I think I'll use the SIGPIPE approach.
    There are already a lot of crons on my server - and the thing about those is I get an email from the root process every time one runs. I think it's more elegant to have the script clean up after itself.

    And thanks to saskaqueer for your post. I didn't even think to use an "or die" statement on a print call. Not enough coffee today ...

    Thanks! You monks are awesome.

      As an aside, if the only thing you don't like about cron is that it emails you the output from the programs it runs, you can stop that by redirecting a program's output to /dev/null like this:
      ... your_script >/dev/null 2>&1
Re: cleanup a cancelled CGI script
by jayrom (Pilgrim) on Jun 08, 2004 at 20:47 UTC
    This will probably not apply directly to your problem as it refers to mod_perl but it is an interesting read nonetheless.

    jayrom

Re: cleanup a cancelled CGI script
by cLive ;-) (Prior) on Jun 08, 2004 at 21:15 UTC
    I may be missing something, but is there anything wrong with fork?
    #!/usr/bin/perl use strict; use warnings; use POSIX ":sys_wait_h"; $|++; print "Content-type:text/plain\n\nStarting script.....\n\n"; my $pid; if (!defined($pid = fork)) { die "Could not fork! $!"; } elsif ($pid) { # parent - clean up when child exits 1 while (waitpid($pid,WNOHANG) != -1); cleanup(); exit(0); } else { # child - does the processing print "Processing...\n\n"; sleep(5); print "Finished...\n\n"; exit(0); } sub cleanup { # do the clean up here }

    In my test CGI $|++ doesn't seem to do anything. I can't work out why the output appears buffered - is that a fork side effect?

    cLive ;-)

Re: cleanup a cancelled CGI script
by Anonymous Monk on Jun 08, 2004 at 21:14 UTC
    try using the sigtrap module
      UPDATE:: I got a lot of good advice after posting this thread - thanks to all. I tried using
      $SIG{PIPE} = sub { ... };
      but found that it wasn't working all the time. It turns out that by hitting the stop button on the browser, a SIGTERM was ending the script in stead.

      I took this anonymous suggestion to use the sigtrap module, and that's working flawlessly now. Thanks!
      use sigtrap qw(die untrapped normal-signals); END { ... here's where I clean up the zip file ... }
      sigtrap