Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

Detect Stop Button

by hakkr (Chaplain)
on Nov 26, 2001 at 16:48 UTC ( [id://127530]=perlquestion: print w/replies, xml ) Need Help??

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

Is it possible for a CGI script to detect the user clicking the stop button in their browser. Something to do with Apache detecting an interupted HTTP request maybe?

I want to kill my script when they click stop instead of it obliviously running away continuing about it's business.

ta much (your humble pupil)

Replies are listed 'Best First'.
Re: Detect Stop Button
by sevensven (Pilgrim) on Nov 26, 2001 at 18:58 UTC

    You need to check if your script has lost access to STDOUT.

    One easy way to make sure your script will stop is to keep it printing info as it processes (and make sure output is not buffered). The script will die as soon as it tries to access STDOUT and it is no longer available.

    I tried to check if the script would get an alarm from Apache, but could not get anything (wich is ok, because Apache docs do not talk about signals to CGI scripts when client disconnects ;^)

    I'll put my test code here, just in case you want to fidget with it a bit. If all output is placed into $out instead of dumping it straight to STDOUT, the CGI will not stop running until it normaly ends :

    #!/usr/bin/perl use strict; print "Content-type: text/plain\n\n"; $| = 1; #dont buffer output to stdout # playing with signals, no luck sub signal { print LOG "got signal, shuting down\n"; close LOG; } $SIG{INT} = \&signal; $SIG{PIPE} = \&signal; $SIG{ALRM} = \&signal; $SIG{HUP} = \&signal; # check until when did the CGI run with log file open(LOG, '>/usr/local/apache/cgi-bin/log.txt') or die("open : $!"); # dont want LOG file buffering my $old_fh = select(LOG); $| = 1; select($old_fh); my $out = "running cgi, placing outout here\n"; for (my $i=0;$i<20;$i++) { # toogle comments in the 2 following lines to see the CGI stop # print "[$i] test cgi<br>\n"; $out .= "[$i]test cgi<br>\n"; print LOG "[$i] test cgi\n"; sleep(1); } print LOG $out; close LOG;

    Update : this sample did not work in IIS (as giulienk reported, tks!), but you only need to add a  or die("gone"); after each print. Perl dies in unix CGI land when it cannot access STDOUT, but it lives on in CGI/IIS windows land. And please remember, the snippet *does not* receive signals from apache, the handlers are there just to show that :-)

    -- sevensven or nana korobi, ya oki
      Uhmmm, i got no luck running your example under Win2k. The script goes on working even if i "stopped" the browser.

      Maybe signal handling differs and require some different libraries under windoze?

      Update: i'm using IIS, maybe that's the problem?

      Nice one

      You have made my first perlmonks post worthwhile.
      Constantly printing info may slow me down but it a price worth paying to kill all those zombie cgi.
        hakkr, sevensven's code is just a test that illustrates how to use those signal handlers, and it will let you verify that your server passes the signals to your script appropriately. You don't need to constantly print anything -- that was just part of the test. So, don't slow yourself down needlessly, okay?

        On the other hand, if your script will run for quite a while -- 2+ minutes -- then you really should be printing some invisible characters to keep the browser from timing out. Just another CGI trick...

        Check out sigtrap. After checking out this doc, I'm about to replace all my signal handler code with one sigtrap line!

        Also, check out the great info in the Camel 3 book pp. 412-417 about handling signals and some more specific info on zombie processes.

        One last thing: SIG{PIPE} is the signal which should get sent when a user hits the STOP button on their browser. You really should handle those other important signals too, but SIG{PIPE} is the enemy you're dealing with specifically.

        Update: Here's my new and improved signal handler.
        use Carp; use sigtrap qw(handler croak normal-signals error-signals);
        Also, the signal I get when I hit the STOP button is actually $SIG{TERM}, not $SIG{PIPE}. So, I lied earlier. :)
(crazyinsomniac) Re: Detect Stop Button
by crazyinsomniac (Prior) on Nov 26, 2001 at 17:36 UTC
    sure it's possible in some situations.

    If you have a script, to which a user is posting a form, and the content-size (or whatever) header says the user is posting 10k, but only 5k gets transmitted, chances are the user hit the stop button.

    That is the only way I see you detecting the user pressing a stop button.

    If you're using CGI.pm, you'll probably want to slap the code to check for this in a BEGIN block, and read off STDIN yourself.

    You probably want to describe better what your script is doing, as there might be other (fork fork) strategies you'd be better suited taking when approaching your particular problem.

     
    ___crazyinsomniac_______________________________________
    Disclaimer: Don't blame. It came from inside the void

    perl -e "$q=$_;map({chr unpack qq;H*;,$_}split(q;;,q*H*));print;$q/$q;"

      My script is writing a bunch of html files to disk created from Mysql searches. It only perfoms the searches and writes the files if it recieves all the form data.
      Thanks anyway I'll try and be more detailed in future.

        Perhaps another approach may work. Depending upon your application, you may want to provide a mechanism for the user to stop the process:

        In your script, create a session key (incorporating the process ID and perhaps hostname of the running script, for example) and store it in a cookie. Before the "meat" of the script begins ("...writing a bunch of html files to disk created from Mysql searches..."), write out some HTML containing a button or a link which when clicked, launches a second script which uses the value in the cookie to identify the first script's process and kills it.

        I realize this is not the solution you asked for. As you can see from the other responses, due to the asynchronous natture of HTTP discourse, there is no completely reliable way to detect browser disconnection.

        Pressing Stop itself is not even reliable: I've seen circumstances with Internet Explorer (v5.5 SP2) where clicking Stop (or hitting Esc) had no immediate effect -- the spinning globe just kept spinning. I haven't made a study of the prevailing conditions at the time, but invariably the browser was waiting for some object or another (i.e., image or ad) to load.

        Perhaps offering users an alternative to waiting might be attractive enough that they'll prefer it over simply browsing away from your results page. This approach won't prevent users from hitting Stop or browsing away from your page, but it might cut down the number of instances of it.

        dmm

        Reply from thread below: My script runs for about 1 hr so I had set my Apache TimeOut variable to a huge value (3600). This seems to work for most browsers. I think I'll try your idea of printing blank chars to STDOUT instead. The TimeOut variable effects all requests and probably leaves me with lots of Apache children hanging around

        Okay, so let me make sure I'm clear on this. You have a CGI script which runs for almost one hour. This cgi script creates a number of static html files to represent results from MySQL searches? Is the user expected to hang around for an hour to view the resulting html pages?

        It sounds like you *really* should be taking in the form information, verifying that you have all the form data, then writing this form data to a file. Then, a cron script should run every so often to see if any form data files exist, and process them accordingly. Then, the cron script could send the user an e-mail upon completion.

        By verifying your form data way before the script even runs, you will save those times where someone says "Oh shoot, I mistyped this field.. let me go back." You also can more appropriately schedule the running of this one hour script to not choke up your system.

        Besides, I can't think of ANY reason that a cgi script should run for longer than 5 minutes or so, and even that is quite silly. No one is going to watch the output for a whole hour, so you might as well e-mail them when it's done.
Re: Detect Stop Button
by Biker (Priest) on Nov 26, 2001 at 18:41 UTC

    Not in any reliable way. Once the http server has received the full request, it is working asyncronously from the client (i.e. the browser). Once your script has received the full request, it is working more or less independently from the http server. And in any way, fully independently and asyncronously from the client browser.

    Think of what your script is really asked to do.
    The normal task for your script is to react upon parameters given to it, following an agreed behaviour, and then write something to stdout.
    Another common agreement says that your script should write a valid HTML page to stdout, whatever that is defined as.

    A CGI script is actually a rather 'dumb' piece of code, with no direct interaction with the client browser.

    The http server, which is capturing what your script wrote, will try to send that output to the IP address that sent the request, to the port indicated in the same request.

    The server may eventually detect that no one received what it sent down to the client (browser) but not even this is absolutely garranteed. (Think proxies, etc.) Anyway, your script would never know, because it has already terminated.

    The user of the client browser may eventually have closed the application (the browser) while your script is working hard to print something to stdout that no one will ever read. :-(

    f--k the world!!!!
    /dev/world has reached maximal mount count, check forced.

Re: Detect Stop Button
by kschwab (Vicar) on Nov 26, 2001 at 20:57 UTC
    I'm not as familiar with Apache as I am with Netscape's web server...but this is the sort of thing that's abstracted from a cgi-bin program.

    Under Netscape's nsapi routines, you can detect a failure in net_write(), which includes the user pressing the stop button.

    It would appear that there is an Apache equivalent, but you would need to be running something like mod_perl to gain access to the Apache "request write" functions.

    From perusing the mod_perl lists, it appears there was a change starting with Apache 1.3.6. So...

    • For pre-1.3.6 Apache: You have to handle SIGPIPE
    • Apache 1.3.6 and later: $r->print() returns false on write failures
    More info found at this url: http://thingy.kcilink.com/modperlguide/debug/Handling_the_User_pressed_Stop_.html

Re: Detect Stop Button
by andye (Curate) on Nov 26, 2001 at 20:30 UTC
    In addition to the fine points made on this page by other monks, you might want to check out the CGI function cgi_error().

    andy.

Re: Detect Stop Button
by cacharbe (Curate) on Nov 27, 2001 at 08:50 UTC
    Why not write the application around a queue?

    You write out a page with a stop button on submit, if it is "pressed", it pushes a stop command at the queue, and after certain process points, your script checks to see if it should stop, or you have a child that looks for the stop. This way, you aren't relying on catching the "Stop" button.

    The constant writing to STDOUT isn't always a good method. Example:

    Our intranet has a throttle, and will also time out connections that last longer than X minutes if the connection is going between buildings. Meanwhile, your CGI script is running, but the STOP button on the browser has been rendered useless.

    The Web is stateless, don't try to circumvent that. Writing the queue also allows you to update a percent finished or some other kind of process flag to provide feedback to your user. Put a meta refresh on the page, and the user now has an idea of WHEN the process will finish, based on this feed back without even having to hit an 'update' button (which you SHOULD provide so that an impatient user can always clickity-click). Voila, a stateful looking connection to a stateful application in a stateless medium.

    C-.

    Update: After reading the above, I realized that my writing looks like I haven't slept in days. It's catching up to me.

Re: Detect Stop Button
by elwarren (Priest) on Nov 26, 2001 at 22:36 UTC
    Off the top of my head I can only offer direction instead of an answer. In my Apache logs we have a "Bytes Sent" column. If you're using mod_perl you may be able to get this value and see whether you sent the entire payload or not.

    If you're doing a form submission and are not opposed to JavaScript you might be able to send an additional field that is a simple checksum/crc type check by adding up values from your other fields. If this field didn't make it to the server, or if it's checksum didn't match yours, then you'd know that it wasn't submitted.

    The sendpage form on skytel.com will calculate the length of the field before you hit submit so that you know if the page you're about to send is too long. You could take that JavaScript code and submit the length of all the fields you're sending as a simple checksum...

    HTH
Re: Detect Stop Button
by monkfish (Pilgrim) on Nov 27, 2001 at 04:50 UTC
    You should get a SIG PIPE either when the user hits stop or when you try and output and the user has previously hit stop. So this should be trappable if you are not inside mod_perl.

    Unbuffering your output $| =1; may make this more time sensitive.

    -monkfish (The Fishy Monk)

Re: Detect Stop Button
by nlafferty (Scribe) on Nov 26, 2001 at 21:41 UTC
    Perhaps you could make a stop button on a form then find away to kill the process if the stop button data is present at any point during fun time. Like a loop that keeps checking for the stop button data. You could break it down ito sections to that it checks after each step in the program.
      I was going to try that as well by writing the pid ($$) to a file when I start up. Thought it might be a bit dangerous though.
        I guess it's alway dangerous when your script is going to issue system commands. There has to be a semi-safe way though.
Re: Detect Stop Button
by Dogma (Pilgrim) on Nov 27, 2001 at 10:30 UTC
    From reading the rest of the posts it seems like an Apache::StopDetect or like module should be a fairly simple task to code. Does anyone know of a pre-existing module to do this or a non-apache specific mechanism (ie content-length being inconsistent)?
      sub StopCheck{ $| = 1; print "\n" or die "STDOUT gone away"; }
      Problem solved I call this subroutine with each iteration of my programs main control loop. The die is redundant in Apache/UNIX as it dies when you print anyway. Works when the user presses stop or navigates away from the page. As it's per filehandle I don't think you have to turn buffering back on.

      Also has the added benefit of keeping the connection alive

      Thanks for all your help.
      Update: Zuqif suggested I adapt this sub to create a progress bar in the browser. Some magic todo with browser refreshing I think is required

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://127530]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (2)
As of 2024-04-20 04:01 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found