I'm trying to build a timeout scenario in my Catalyst, AnyEvent, Websocket app.
For that I'm using
AnyEvent->timer
which should be called after let's say a few seconds of inactivity (no more WS frames coming in).
The problem is, that my timer is never executed:
my $w = AnyEvent->timer (after => 3,
cb => sub {
warn "TIMEOUT!";
});
$self->{server} = Protocol::WebSocket::Handshake::Server->new_from_psgi(
$c->req->env) or die $c->log->fatal($!);
$self->{handle} = AnyEvent::Handle->new(
fh => $c->req->io_fh,
on_error => sub {
my ($hd, $fatal, $msg) = #_;
$clean_up->();
}
);
die $c->log->fatal("WS Server error: '$_'")
if $self->{server}->error;
$self->{server}->parse($self->{handle}->fh);
$self->{handle}->push_write($self->{server}->to_string);
$self->{handle}->on_read(sub {
(my $frame = $self->{server}->build_frame)->append($_[0]->rbuf);
while (my $frame_msg = $frame->next) {
...
}
The timer callback is never executed.
My guess would be, that the timer doesn't work inside another Event loop (AnyEvent::Handle)?
Are you actually getting into the event loop for the timer to be processed? Your code snippet does not indicate this.
Also, AnyEvent::Handle has inactivity timeouts built-in:
timeout => $fractional_seconds
If non-zero, then this enables an "inactivity" timeout: whenever
this many seconds pass without a successful read or write on the
underlying file handle, the "on_timeout" callback will be invoked
(and if that one is missing, a non-fatal "ETIMEDOUT" error will
be raised).
Note that timeout processing is also active when you currently do
not have any outstanding read or write requests: If you plan to
keep the connection idle then you should disable the timout
temporarily or ignore the timeout in the "on_timeout" callback,
in which case AnyEvent::Handle will simply restart the timeout.
Zero (the default) disables this timeout.
on_timeout => $cb->($handle)
Called whenever the inactivity timeout passes. If you return from
this callback, then the timeout will be reset as if some activity
had happened, so this condition is not fatal in any way.
Related
The nutshell:
When I do a hot deployment of Hypnotoad sometimes the new server immediately processes a slew of HTTP requests that were already handled by the previous server.
If a response has been rendered but the thread is still doing some processing does Mojo/Hypnotoad retain the request until the processing has stopped? Do I need to tell the server that the HTTP request is resolved?
The long version:
I have a Mojolicious::Lite app running under Hypnotoad.
The app's function is to accept HTTP requests from another service.
We are processing jobs that progress through a series of states.
At each job state change the app is notified with an HTTP request.
This is a busy little script - recieving more than 1000 req/hour.
The scripts job is to manipulate some data .. doing DB updates, editng files, sending mail.
In an effort to keep things moving along, when it recieves the HTTP request it sanity checks the data it recieved. If the data looks good it sends a 200 response to the caller immediately and then continues on to do the more time consuming tasks. (I'm guessing this is the underlying cause)
When I hot deploy - by rerunning the start script (which runs 'localperl/bin/hypnotoad $RELDIR/etc/bki/bki.pl') - some requests that were already handled are sent to the new server and reprocessed.
Why are these old transactions still being held by the original server? Many have been long since completed!
Does the need to tell Mojolicious that the request is done before it goes off and messes with data?
(I considered $c->finish() but that is just for sockets?)
How does Hypnotoad decide what requests should be passed to it's replacement server?
Here is some psuedo code with what I'm doing:
get '/jobStateChange/:jobId/:jobState/:jobCause' => sub {
my $c =shift;
my $jobId = $c->stash("jobId");
return $c->render(text => "invalid jobId: $jobId", status => 400) unless $jobId=~/^\d+$/;
my $jobState = $c->stash("jobState");
return $c->render(text => "invalid jobState: $jobState", status => 400) unless $jobState=~/^\d+$/;
my $jobCause = $c->stash("jobCause");
return $c->render(text => "invalid jobCause: $jobCause", status => 400) unless $jobCause=~/^\d+$/;
my $jobLocation = $c->req->param('jobLocation');
if ($jobLocation){ $jobLocation = $ENV{'DATADIR'} . "/jobs/" . $jobLocation; }
unless ( $jobLocation && -d $jobLocation ){
app->log->debug("determining jobLocation because passed job jobLocation isn't useable");
$jobLocation = getJobLocation($jobId);
$c->stash("jobLocation", $jobLocation);
}
# TODO - more validation? would BKI lie to us?
return if $c->tx->res->code && 400 == $c->tx->res->code; # return if we rendered an error above
# tell BKI we're all set ASAP
$c->render(text => 'ok');
handleJobStatusUpdate($c, $jobId, $jobState, $jobCause, $jobLocation);
};
sub handleJobStatusUpdate{
my ($c, $jobId, $jobState, $jobCause, $jobLocation) = #_;
app->log->info("job $jobId, state $jobState, cause $jobCause, loc $jobLocation");
# set the job states in jobs
app->work_db->do($sql, undef, #params);
if ($jobState == $SOME_JOB_STATE) {
... do stuff ...
... uses $c->stash to hold data used by other functions
}
if ($jobState == $OTHER_JOB_STATE) {
... do stuff ...
... uses $c->stash to hold data used by other functions
}
}
Your request will not be complete until the request handler returns. This little app, for example, will take 5 seconds to output "test":
# test.pl
use Mojolicious::Lite;
get '/test' => sub { $_[0]->render( text => "test" ); sleep 5 };
app->start;
The workaround for your app would be to run handleJobStatusUpdate in a background process.
get '/jobStateChange/:jobId/:jobState/:jobCause' => sub {
my $c =shift;
my $jobId = $c->stash("jobId");
my $jobState = $c->stash("jobState");
my $jobCause = $c->stash("jobCause");
my $jobLocation = $c->req->param('jobLocation');
...
$c->render(text => 'ok');
if (fork() == 0) {
handleJobStatusUpdate($c, $jobId, $jobState, $jobCause, $jobLocation);
exit;
}
How do I properly use a timeout when I attempt to make ->get(URL) requests with WWW::Mechanize::Firefox?
my $mech = WWW::Mechanize::Firefox->new(timeout => 10); does not seem to work
It is possible to simulate this, at least to a good extent.
You can turn off synchronization for get, in which case the call should return immediately. Then poll every $sleep_time until timeout, with some test of whether the page completed. The sleep allows all those other good pages to complete, so set $sleep_time as appropriate.
my $timeout = 10;
my $sleep_time = 1;
my $page = get($url, synchronize => 0);
for (1..$timeout) {
# Test some page property that will confirm that it loaded
last if $page->title eq $expected_title;
sleep $sleep_time;
}
There is the issue of how exactly to confirm each page, but this should provide a working timeout.
I am using the following code. I want to timeout and close the connection after 20 seconds, tried with alarms but nothing worked. Here is my code:
my $socket_resp = IO::Socket::INET->new(Blocking => 0, LocalPort => $comm_port, Proto => 'udp', Timeout => 2);
$socket_resp->setsockopt(SO_RCVTIMEO, SO_RCVTIMEO, 10);
print "Waiting for Response On Port $comm_port\n";
while (my $recieved_data = $socket_resp->getline()) {
chomp($recieved_data);
print "$recieved_data\n";
if ($recieved_data =~ m/^done/i) {
last;
}
}
$socket_resp->close();
Wrapping your entire read loop in an alarm, as suggested in the other question will very likely do what you want. You don't show us code, so we don't know why your previous attempts failed.
That said, SO_RCVTIMEO can be made to work, too, albeit a bit differently.
You want a blocking rather than non-blocking socket in this case. You also want to setsockopt correctly, which requires SOL_SOCKET and pack()ing a struct timeval:
my $s = IO::Socket::INET->new(Proto => 'udp', ...); # N.B.: blocking
$s->setsockopt(SOL_SOCKET, SO_RCVTIMEO, pack('l!l!', 20, 0)); # N.B.: pack()
while (<$s>) {
...
}
Now, the above waits 20 seconds for each underlying call to read(), which may be more than the number of lines returned to your application. That is, if I send your application "foo\n" in one datagram and then nothing, you'll timeout after 20 seconds. However, I might send "f", then wait 19 seconds, "o", then wait 19 seconds, "o", then wait 19 seconds, ... you get the idea.)
I'm writing a script to assist people who'll scan a barcode and get a response to keep or dispose the scanned sample. I want to have a message, similar to tk's messagebox or Win32::MsgBox but one that requires no user interaction to go away after three seconds.
My thought was to create the messages in a child process, using alarm to kill the process after a delay. In Tk:
sub tmpMsgBox {
my ($message,$delay) = #_;
if (fork() == 0) {
my $topWin = MainWindow->new;
my $label = $topWin->Label();
my $ok = $topWin->Button();
$label->pack(-side => 'top');
$ok->pack(-side => 'bottom');
$label->configure(-text => $message);
$ok->configure(-text => 'Ok', -command => sub {exit});
$SIG{ALRM} = sub {exit};
alarm $delay || 1;
$topWin->MainLoop;
}
}
for (3..10) {
tmpMsgBox("This window will disappear in $_ seconds", $_);
}
I don't think Tk plays nicely with fork, though, so this idea probably won't work so well if you are also using Tk in your main process.
Desktop::Notify is the standard-compliant interface to the desktop's passive notification pop-ups.
perl -MDesktop::Notify -e'
Desktop::Notify
->new
->create(
body => q{why hello there},
timeout => 3000
)->show'
What you want to do is to send a destroy message to the window after your timeout (remembering to cancel the sending of the message if the user does choose something!) Tk's certainly capable of doing this.
# Make the timeout something like this...
$id = $widget->after(3000, sub {
$widget->destroy;
});
# To cancel, just do...
$id->cancel;
You also need to make sure that you don't block when the widget is forced to go away, of course. This also prevents trouble if someone kills the widget by other means too, so it's a double-bonus.
I am trying to make my own Jabber bot but i have run into a little trouble. I have gotten my bot to respond to messages, however, if I try to change the bot's presence then it seems as though all of the messages you send to the bot get delayed.
What I mean is when I run the script I change the presence so I can see that it is online. Then when I send it a message it takes three before the callback subroutine I have set up for messages gets called. After the thirrd message is sent and the chat subroutine is called it still process the first message I sent.
This really doesn't pose too much of a problem except that I have it set up to log out when I send the message "logout" and it has to be followed by two more messages in order to log out. I am not sure what it is that I have to do to fix this but i think it has something to do with iq packets because I have an iq callback set as well and it gets called two times after setting the presence.
Here is my source code:
#!/usr/bin/perl
use strict;
use warnings;
#Libraries
use Net::Jabber;
use DBI;
use DBD::mysql;
#--------------- Config Vars -----------------
# Jabber Client
my $jbrHostname = "DOMAINNAME";
my $jbrUserName = "USERNAME";
my $jbrPassword = "PASSWORD";
my $jbrResource = "RESOURCE";
my $jbrBoss = new Net::Jabber::JID();
$jbrBoss->SetJID(userid=>"USERNAME",server=>$jbrHostname);
# MySQL
my $dbHostname = "DOMAINNAME";
my $dbName = "DATABASENAME";
my $dbUserName = "USERNAME";
my $dbPassword = "PASSWORD";
#--------------- End Config -----------------
# connect to the db
my $dbh = DBI->connect("DBI:mysql:database=$dbName;host=$dbHostname",$dbUserName, $dbPassword, {RaiseError => 1}) or die "Couldn't connect to the database: $!\n";
# create a new jabber client and connect to server
my $jabberBot = Net::Jabber::Client->new();
my $status = $jabberBot->Connect(hostname=>$jbrHostname) or die "Cannot connect ($!)\n";
my #results = $jabberBot->AuthSend(username=>$jbrUserName,password=>$jbrPassword,resource=>$jbrResource);
if($results[0] ne "ok")
{
die "Jabber auth error #results\n";
}
# set jabber bot callbacks
$jabberBot->SetMessageCallBacks(chat=>\&chat);
$jabberBot->SetPresenceCallBacks(available=>\&welcome);
$jabberBot->SetCallBacks(iq=>\&gotIQ);
$jabberBot->PresenceSend(type=>"available");
$jabberBot->Process(1);
sub welcome
{
$jabberBot->MessageSend(to=>$jbrBoss->GetJID(),subject=>"",body=>"Hello There!",type=>"chat",priority=>10);
&keepItGoing;
}
$jabberBot->MessageSend(to=>$jbrBoss->GetJID(),subject=>"",body=>"Hello There! Global...",type=>"chat",priority=>10);
#$jabberBot->Process(5);
&keepItGoing;
sub chat
{
print "Chat Called!\n";
my ($sessionID,$msg) = #_;
$jabberBot->MessageSend(to=>$msg->GetFrom(),subject=>"",body=>"Chatting!",type=>"chat",priority=>10);
if($msg->GetBody() ne 'logout')
{
print $msg->GetBody()."\n";
&keepItGoing;
}
else
{
&killBot($msg);
}
}
sub gotIQ
{
print $_[1]->GetID()."\n";
&chat;
}
sub keepItGoing
{
print "Movin' the chains!\n";
my $proc = $jabberBot->Process(1);
while(defined($proc) && $proc != 1)
{
$proc = $jabberBot->Process(1);
}
}
sub killBot
{
$jabberBot->MessageSend(to=>$_[0]->GetFrom(),subject=>"",body=>"Logging Out!",type=>"chat",priority=>10);
$jabberBot->Process(1);
$jabberBot->Disconnect();
exit;
}
Thanks for your help!
You've got resource starvation because of your keepItGoing routine. In general, trying to use XMPP synchronously like this is not going to work. I suggest getting your callbacks set up, then just calling Process() in one loop.
The docs for Process() say:
Process(integer) - takes the timeout period as an argument. If no
timeout is listed then the function blocks until
a packet is received. Otherwise it waits that
number of seconds and then exits so your program
can continue doing useful things. NOTE: This is
important for GUIs. You need to leave time to
process GUI commands even if you are waiting for
packets. The following are the possible return
values, and what they mean:
1 - Status ok, data received.
0 - Status ok, no data received.
undef - Status not ok, stop processing.
IMPORTANT: You need to check the output of every
Process. If you get an undef then the connection
died and you should behave accordingly.
Each time you call Process(), 0 or more of your callbacks will fire. You never know which, since it depends on server timing. If you want for Process() to return before sending something, you're almost always thinking synchronously, rather than asych, which kills you in XMPP.
In your case, if you remove the call to keepItGoing from chat(), I bet things will work more like you expect.
Replace the line:
$jabberBot->Process(1);
with these:
while (defined($jabberBot->Process(1))) {
# Do stuff here
}