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

Hi everybody,

I wrote this little cron script that deletes any expired sessions that happen to still be on disk.

I'm using CGI::Session to handle my sessions and everything seems to be going well except for this error I keep getting when running the cron script.
disconnect invalidates 1 active statement handle DBI::db=HASH(0x8320754)->disconnect invalidates 1 active statement han +dle (either destroy statement handles or call finish on them before d +isconnecting)
I have no clue what I'm doing wrong...and can only assume its something to do with the new 4.20 CGI::Session module.

The following uses the new load method to check for expired sessions:
use DBI; my $dbh = DBI->connect($location, $user, $ba_pass, { RaiseError => + 1, AutoCommit => 1}) or die "Couldn't connect to database: " . $DBI: +:errstr; eval { my $sth= $dbh->prepare("SELECT id FROM sessions"); my $id; $sth->execute; $sth->bind_columns(undef, \$id); my $f = 0; while ($sth->fetch) { $f++; if ($f == 1) { require CGI::Session; import CGI::Session; $CGI::Session::IP_MATCH = 0; } my $session = CGI::Session->load("driver:MySQL", + $id, {Handle=>$dbh}); if ($session->is_expired) { $session->delete(); $session->close(); } } $dbh->disconnect; }; if ($@) { print $@; $dbh->disconnect; }
I did also add $sth->finish before calling disconnect, but it still gives me the same error. So its not the $sth handle...also I do know theres no need to call $sth->finish if DBI knows when the fetching suppose to stop. So my question is, whats causing this error? How can I further debug what handle is active and not being finished?

UPDATE:
figured out how to check for active handles by using ActiveKids.

Thanks,
perleager

Replies are listed 'Best First'.
Re: CGI::Session disconnect invalidates 1 active statement handle
by whereiskurt (Friar) on Jul 06, 2007 at 01:03 UTC

    Bonjour:

    I think I understand your motivations, maybe it's because you have a 'lingering' session issue - you serve up a sessionid in a cookie and the user goes away forever (i.e. clears their cookies.) That session never expires because they never come back. Before you know it your session table is poopy big. :-)

    Advice? Take that whole block out of 'eval {}' and watch for errors. Also, why load the session at all why not just fire off a 'DELETE FROM Session where id=$id' on te $sth?

    Good Luck!
    KPH

Re: CGI::Session disconnect invalidates 1 active statement handle
by whereiskurt (Friar) on Jul 07, 2007 at 13:20 UTC

    perleager:

    I was reading the CGI::Session manual (like a good little developer does :-)) and I noticed that you are using the DEPRECATED method 'close()' (the horror!!)

    Currently you call 'load()' (passing in the ID) and then call 'is_expired()', and then, if it's expired, call delete(). My thought is that CGI::Session, when it calls the 'load()' it may very well call the 'is_expired()' off the hop. Are you 100% sure that after load()ing an 'expired session' that CGI::Session isn't doing data store cleanup? (ie. already removing it)

    IMO - I think you should write your own 'is_expired()' sub that inspects a Session.ID using DBI, which returns true/false. Write another sub that takes a Session.ID and deletes it's using DBI (e.g execute SP or DELETE statement.) Once you've got those pieces done, your CRON job loops front to back on the Session table, passing each ID to the 'is_expired()' sub, calling the delete when true. Conceptually what you have already but avoid the load()ing of the already expired session.

    Maybe I'll just have to write some code to show you what I mean.... if you're even still reading this thread. ;-)

    Kurt

Re: CGI::Session disconnect invalidates 1 active statement handle
by jZed (Prior) on Jul 05, 2007 at 21:40 UTC
    If you seriously want people to read your code, please indent it in a fashion that is legible.
      Huh? I'd say the example was above average for legibility - you should see some of the legacy Perl code I have had to maintain over the years. It's true that one of the lines is two characters too far (oh calamity!) and that the tabbing isn;t in perfect multiples (oh the shame of it ;)). But at least reasonable effort appears to have been made in regard to appearance albeit not up perltidy's exacting standards. I wouldn't say it goes anywhere near far astray enough to deserve your implied threat, however.
      __________________________________________________________________________________

      ^M Free your mind!

        I guess I was too curt, that wasn't an implied threat, it was advice on making things easier on cow orkers or others. It was based on this: I tried to figure out if the disconnect was in the right place in the loop and I had to manually count the braces to do that. Better indenting would have made it obvious at a glance.
Re: CGI::Session disconnect invalidates 1 active statement handle
by Moron (Curate) on Jul 06, 2007 at 10:19 UTC
    It's been a while since I did this, but as far as I can recall, CGI::Session is CGI::Cookie-aware, so that specifically issuing a CGI::Cookie with an expiry date should make it DWIM.
    __________________________________________________________________________________

    ^M Free your mind!

Re: CGI::Session disconnect invalidates 1 active statement handle
by perleager (Pilgrim) on Jul 08, 2007 at 00:52 UTC
    Hi everyone. I realized the close() method was deprecated with flush when reading the docs. I remember actually using flush() first just to see if it would get rid of the error, then decided to try close for the heck of it.

    As with jZed, I'll take your advice and try to better indent my coding.

    I decided not to use cookies and have it keep loading the session via db (sessions expire after 30mins no matter what..this app will not be used my many).

    Kurt, I think I know what you mean. I can prob modify CGI::Session so that it adds a expire_time field to the database, and then I can just check against that time vs, the E_TIME stored by CGI::Session. I think I read an example here, Re: Listing Active CGI::Sessions, before writing this little cron script.

    I was going to do this option, but thought it wasn't too necessary if I could get my first method right.

    I fixed my following code so it looks like this:
    use DBI; my $dbh = DBI->connect($main::location, $main::user, $main::ba_pas +s, { RaiseError => 1, AutoCommit => 1}) or die "Couldn't connect to d +atabase: " . $DBI::errstr; eval { my $sth= $dbh->prepare("SELECT id FROM sessions"); my $id; $sth->execute(); $sth->bind_columns(undef, \$id); require CGI::Session; import CGI::Session; while ($sth->fetch()) { my $session = CGI::Session->load("driver:MySQL", $id, {Handle=>$ +dbh}); next if $session->is_empty; if (($session->ctime + $session->etime) <= time()) { $session->delete(); } } }; if ($@) { print $@; $dbh->disconnect(); }
    Still a bit concerned since it doesn't have $dbh->disconnect anymore inside the eval. I'm relying on it existing and disconnecting when it detects the end of my script. The above code generates no errors or warnings and my cron script can run w/out e-mailing me the output of the error all the time. At most, it will only detect about 3-20 sessions a given day.

    Notice I still use the load method vs. the new method of CGI::Session. I thought the load method was intentionally for this purpose (to only load and read vs, using new to create/edit/append).

    From the CGI::Session manual:
    load() is useful to detect expired or non-existing sessions without forcing the library to create new sessions.

    Also , I hope my indenting is a bit better. :)
      Hi perleager. I didn't mean to sound too harsh, and it's not like indenting is the most important thing and tastes vary. But in a chunk of code like you have, I find it most useful to indent mostly based on blocks -- start the indent with the opening "{" of the block and end the indent with the closing "}". This lets you see at a glance which block each line is part of and also to easily check to make sure you didn't omit a curly brace. I sometimes also indent arguments to functions using the opening and closing parentheses to see where to indent. Here's how I would indent your code (again, tastes vary, this is just my way):
      use DBI; my $dbh = DBI->connect( $main::location, $main::user, $main::ba_pass, {RaiseError=>1,AutoCommit=>1} ) or die "Couldn't connect to database: " . $DBI::errstr; # # indent one level for the eval block # eval { my $sth= $dbh->prepare("SELECT id FROM sessions"); my $id; $sth->execute(); $sth->bind_columns(undef, \$id); require CGI::Session; import CGI::Session; # # indent a second level for the while block # while ($sth->fetch()) { my $session = CGI::Session->load( "driver:MySQL", $id, {Handle=>$dbh} ); next if $session->is_empty; # # indent a third level for the if block # if (($session->ctime + $session->etime) <= time()) { $session->delete(); } } }; if ($@) { print $@; $dbh->disconnect(); }

      update:renamed it so I could find it :-)

Re: CGI::Session disconnect invalidates 1 active statement handle
by snopal (Pilgrim) on Jul 06, 2007 at 13:42 UTC
    Re: CGI::Session disconnect invalidates 1 active statement handle
    by Anonymous Monk on Oct 20, 2008 at 23:16 UTC
      my $conexion = DBI->connect($source, $user, $passwd, {'RaiseError' => 1}); $query = $conexion->prepare("SELECT * FROM table"); $query->execute(); $resultado = $query->fetchrow_hashref(); $query->finish (); $conexion->disconnect ();
        The best answer is in the previous post than mine, I'd only explain it: You need to call this piece of code $query->finish (); atripathi at ea dot com
          $query->finish (); is the answer, thx guys.