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

I've had problems for a while with users clicking on Post buttons multiple times because the system doesn't respond quickly. This is code for forum software, so it can result in mutiple posts. To counter that, I've added some duplicate post code to check, but that doesn't always work either esspecially if it's because the db has a lock that takes a while to clear.

I've isolated all the code that can take a while and return all the output to the user first. However, it seems like that doesn't always work. This morning I was also testing file locks and noticed that same problem, no output until the script completes. Thankfully, the file lock seems rock solid. However, is there some magic thing that would force the broswer to output each as it gets it?

Here's a sample of the code that I'm testing:

#!/usr/bin/perl -w $|=1; ## Don't buffer output use CGI; use Fcntl qw(:flock); my $file = 'tmp/test_lock.txt'; my $SEMAPHORE = $file . '.lck'; # Output Early for debug my $q = new CGI; print $q->header; my $x; for ($x = 0; $x < 50; $x++) { print "<!-- FORCE STREAM -->\n"; } for ($x = 0; $x < 10; $x++) { print "Line $x<BR>\n"; sleep 1; } print "Getting lock<br>\n"; open(S, ">$SEMAPHORE") or die "$SEMAPHORE: $!"; flock(S, LOCK_EX) or die "flock() failed for $SEMAPHORE: $!"; print "Got lock<br>\n"; my $l; my @in; if (open (FH, "$file")) { @in = <FH>; close FH; foreach $_ (@in) { if ($_ =~ /(.*): I have/) { $l = $1; } } $l = $l+1 if $l; } else { print "Can't open $file: $!"; } $l = 1 unless $l; push (@in,"$l: I have written ($$) flock\n"); open (FH, ">$file") or die "Can't open $file: $!"; print "About to write<br>\n"; print FH @in; print "Written: $l<br>\n"; close FH; print "Going to sleep...\n"; for ($x = 0; $x < 50; $x++) { print "$x ... <!-- FORCE STREAM --><br>\n"; sleep 1; } print "Woken up...<br>\n"; close S;

Yes, I know that I could simplify the file parsing, but I wanted to simulate looking for a specifc line in a large file and having a fairly big chunk of open time to be sure that the file didn't get clobbered. The issue is output. I'd like to see the number 0 to 49 poping up once a second. But what I see is nothing for a minute and then all the output at once.

Thanks!

Replies are listed 'Best First'.
Re: Forcing output to a browser when perl is busy
by ccn (Vicar) on Jul 17, 2004 at 19:49 UTC
    The common way to avoid doubled data sending is the following:
    1. use javascript to disallow form submitting more than once
    2. use hidden field with unique value for each form generated
    3. redirecting a browser to other page as soon as data come
      Hmmm, the Javascript sounds interesting along with a redirect. Do you know of any examples of that type of script that I can steal, er, borrow.
        <script language="Javascript1.1"> function justOneClick() { myform.clickme.disabled=true; // don't click me twice return true; // so the submit will go ahaed } </script> ... <form method="POST" name="myform" onSubmit="return justOneClick()"> ... <input type="submit" name ="clickme"> </form>
        something like this
        <form onsubmit="if(this.submitted){ alert("please be patient"); return + false;}; this.submitted=true"> ... </fode>
        Here's a transfer chunk.. time is in millisecs

        <script> transfer_location = "http://new_url/"; pause_time = 600; function transfer() { if (document.images) setTimeout('location.replace("'+transfer_location+'");',pause_tim +e); else setTimeout('location.href = transfer_location;',pause_time); } </script>


        IMHO the Javascript approach is the least interesting route. What if:
        • The user disabled Javascript
        • The connection to the server really did fail. This is something people visiting Perlmonks should be familiar with... ;-) You're only going to annoy users, who'll lose posts this way.
        In my eye, using a unique (session) id per post, be it submitted or not, is the only way. The server can then choose to either reject posts with the same id, or use it to update an earlier submission (by the same user!)

        You can combine that with a quick response page, which might even automatically reload after a short while. See merlyn's WebTechniques column "Search in progress" page for some ideas.

Re: Forcing output to a browser when perl is busy
by hbo (Monk) on Jul 17, 2004 at 20:16 UTC
    The Javscript approach is the way to go. An onSubmit handler can disable the submit button, then submit the form.

    You can't do this sort of thing purely on the server side without maintining state. Since HTTP itself is is stateless (each transaction is unreleated to any other as far as the protocol is concerned) you have to build state on top, like with cookies. This adds complexity, even if you hide the gory details with things like CGI::Session. For this problem, the client side solution is simpler, even if it isn't in Perl. 8)